diff options
Diffstat (limited to 'src/partest')
33 files changed, 1864 insertions, 2148 deletions
diff --git a/src/partest/README b/src/partest/README index 0434aa7499..17594dbb1e 100644 --- a/src/partest/README +++ b/src/partest/README @@ -24,7 +24,6 @@ Other arguments: * --run next files test the interpreter and all backends * --jvm next files test the JVM backend * --res next files test the resident compiler - * --buildmanager next files test the build manager * --shootout next files are shootout tests * --script next files test the script runner * ''-Dpartest.scalac_opts=...'' -> add compiler options diff --git a/src/partest/scala/tools/partest/BytecodeTest.scala b/src/partest/scala/tools/partest/BytecodeTest.scala index 2699083069..172fa29189 100644 --- a/src/partest/scala/tools/partest/BytecodeTest.scala +++ b/src/partest/scala/tools/partest/BytecodeTest.scala @@ -2,10 +2,9 @@ package scala.tools.partest import scala.tools.nsc.util.JavaClassPath import scala.collection.JavaConverters._ -import scala.tools.asm -import asm.{ ClassReader } -import asm.tree.{ClassNode, MethodNode, InsnList} -import java.io.InputStream +import scala.tools.asm.{ClassWriter, ClassReader} +import scala.tools.asm.tree.{ClassNode, MethodNode, InsnList} +import java.io.{FileOutputStream, FileInputStream, File => JFile, InputStream} import AsmNode._ /** @@ -127,3 +126,31 @@ abstract class BytecodeTest extends ASMConverters { new JavaClassPath(containers, DefaultJavaContext) } } + +object BytecodeTest { + /** Parse `file` as a class file, transforms the ASM representation with `f`, + * and overwrites the orginal file. + */ + def modifyClassFile(file: JFile)(f: ClassNode => ClassNode) { + val rfile = new reflect.io.File(file) + def readClass: ClassNode = { + val cr = new ClassReader(rfile.toByteArray()) + val cn = new ClassNode() + cr.accept(cn, 0) + cn + } + + def writeClass(cn: ClassNode) { + val writer = new ClassWriter(0) + cn.accept(writer) + val os = rfile.bufferedOutput() + try { + os.write(writer.toByteArray) + } finally { + os.close() + } + } + + writeClass(f(readClass)) + } +} diff --git a/src/partest/scala/tools/partest/CompilerTest.scala b/src/partest/scala/tools/partest/CompilerTest.scala index 848deef8c5..df4a81dee2 100644 --- a/src/partest/scala/tools/partest/CompilerTest.scala +++ b/src/partest/scala/tools/partest/CompilerTest.scala @@ -21,7 +21,7 @@ abstract class CompilerTest extends DirectTest { lazy val global: Global = newCompiler() lazy val units: List[global.CompilationUnit] = compilationUnits(global)(sources: _ *) import global._ - import definitions._ + import definitions.{ compilerTypeFromTag } override def extraSettings = "-usejavacp -d " + testOutput.path @@ -32,7 +32,6 @@ abstract class CompilerTest extends DirectTest { def sources: List[String] = List(code) // Utility functions - class MkType(sym: Symbol) { def apply[M](implicit t: ru.TypeTag[M]): Type = if (sym eq NoSymbol) NoType @@ -50,7 +49,7 @@ abstract class CompilerTest extends DirectTest { } class SymsInPackage(pkgName: String) { - def pkg = rootMirror.getRequiredPackage(pkgName) + def pkg = rootMirror.getPackage(pkgName) def classes = allMembers(pkg) filter (_.isClass) def modules = allMembers(pkg) filter (_.isModule) def symbols = classes ++ terms filterNot (_ eq NoSymbol) diff --git a/src/partest/scala/tools/partest/DirectTest.scala b/src/partest/scala/tools/partest/DirectTest.scala index 8fcaa6423c..2e6c3baa02 100644 --- a/src/partest/scala/tools/partest/DirectTest.scala +++ b/src/partest/scala/tools/partest/DirectTest.scala @@ -7,9 +7,9 @@ package scala.tools.partest import scala.tools.nsc._ import settings.ScalaVersion -import io.Directory -import util.{ SourceFile, BatchSourceFile, CommandLineParser } +import util.{ SourceFile, BatchSourceFile } import reporters.{Reporter, ConsoleReporter} +import scala.tools.cmd.CommandLineParser /** A class for testing code which is embedded as a string. * It allows for more complete control over settings, compiler @@ -22,8 +22,8 @@ abstract class DirectTest extends App { def show(): Unit // the test file or dir, and output directory - def testPath = io.File(sys.props("partest.test-path")) - def testOutput = io.Directory(sys.props("partest.output")) + def testPath = SFile(sys.props("partest.test-path")) + def testOutput = Directory(sys.props("partest.output")) // override to add additional settings with strings def extraSettings: String = "" @@ -43,10 +43,7 @@ abstract class DirectTest extends App { newCompiler(settings) } - def newCompiler(settings: Settings): Global = { - if (settings.Yrangepos.value) new Global(settings, reporter(settings)) with interactive.RangePositions - else new Global(settings, reporter(settings)) - } + def newCompiler(settings: Settings): Global = Global(settings, reporter(settings)) def reporter(settings: Settings): Reporter = new ConsoleReporter(settings) diff --git a/src/partest/scala/tools/partest/IcodeTest.scala b/src/partest/scala/tools/partest/IcodeTest.scala index f5333cc5f9..b12ec0de61 100644 --- a/src/partest/scala/tools/partest/IcodeTest.scala +++ b/src/partest/scala/tools/partest/IcodeTest.scala @@ -5,9 +5,7 @@ package scala.tools.partest -import scala.tools.nsc._ -import nest.FileUtil._ -import io.Directory +import scala.tools.partest.nest.FileUtil.compareContents /** A trait for testing icode. All you need is this in a * partest source file: diff --git a/src/partest/scala/tools/partest/JavapTest.scala b/src/partest/scala/tools/partest/JavapTest.scala new file mode 100644 index 0000000000..3cb3dc6ca8 --- /dev/null +++ b/src/partest/scala/tools/partest/JavapTest.scala @@ -0,0 +1,26 @@ + +package scala.tools.partest + +import scala.util.{Try,Success,Failure} +import java.lang.System.{out => sysout} + +/** A trait for testing repl's javap command + * or possibly examining its output. + */ +abstract class JavapTest extends ReplTest { + + /** Your Assertion Here, whatever you want to bejahen. + * Assertions must be satisfied by all flavors of javap + * and should not be fragile with respect to compiler output. + */ + def yah(res: Seq[String]): Boolean + + def baddies = List(":javap unavailable", ":javap not yet working") + + // give it a pass if javap is broken + override def show() = try { + val res = eval().toSeq + val unsupported = res exists (s => baddies exists (s contains _)) + assert ((unsupported || yah(res)), res.mkString("","\n","\n")) + } catch { case ae: AssertionError => ae.printStackTrace(sysout) } +} diff --git a/src/partest/scala/tools/partest/PartestDefaults.scala b/src/partest/scala/tools/partest/PartestDefaults.scala index a21c602d14..8478edeb4d 100644 --- a/src/partest/scala/tools/partest/PartestDefaults.scala +++ b/src/partest/scala/tools/partest/PartestDefaults.scala @@ -1,15 +1,11 @@ package scala.tools package partest -import nsc.io.{ File, Path, Directory } -import scala.tools.util.PathResolver -import nsc.Properties.{ propOrElse, propOrNone, propOrEmpty } -import java.lang.Runtime.getRuntime +import scala.concurrent.duration.Duration +import scala.tools.nsc.Properties.{ propOrElse, propOrNone, propOrEmpty } +import java.lang.Runtime.{ getRuntime => runtime } object PartestDefaults { - import nsc.Properties._ - private def wrapAccessControl[T](body: => Option[T]): Option[T] = - try body catch { case _: java.security.AccessControlException => None } def testRootName = propOrNone("partest.root") def srcDirName = propOrElse("partest.srcdir", "files") @@ -25,7 +21,8 @@ object PartestDefaults { def testBuild = propOrNone("partest.build") def errorCount = propOrElse("partest.errors", "0").toInt - def numThreads = propOrNone("partest.threads") map (_.toInt) getOrElse getRuntime.availableProcessors + def numThreads = propOrNone("partest.threads") map (_.toInt) getOrElse runtime.availableProcessors + def waitTime = propOrNone("partest.timeout") map (Duration.apply) getOrElse Duration("4 hours") - def timeout = "1200000" + //def timeout = "1200000" // per-test timeout } diff --git a/src/partest/scala/tools/partest/PartestTask.scala b/src/partest/scala/tools/partest/PartestTask.scala index dc40f9f81b..8b88021dbf 100644 --- a/src/partest/scala/tools/partest/PartestTask.scala +++ b/src/partest/scala/tools/partest/PartestTask.scala @@ -10,14 +10,10 @@ package scala.tools package partest import scala.util.Properties.setProp -import scala.tools.nsc.io.{ Directory, Path => SPath } -import nsc.util.ClassPath -import util.PathResolver import scala.tools.ant.sabbus.CompilationPathProperty -import java.io.File import java.lang.reflect.Method import org.apache.tools.ant.Task -import org.apache.tools.ant.types.{Path, Reference, FileSet} +import org.apache.tools.ant.types.{ Reference, FileSet} import org.apache.tools.ant.types.Commandline.Argument import scala.tools.ant.ScalaTask @@ -27,97 +23,42 @@ import scala.tools.ant.ScalaTask * - `srcdir`, * - `classpath`, * - `classpathref`, - * - `showlog`, - * - `showdiff`, * - `erroronfailed`, * - `javacmd`, * - `javaccmd`, * - `scalacopts`, - * - `timeout`, * - `debug`, * - `junitreportdir`. * * It also takes the following parameters as nested elements: * - `compilationpath`. - * - `postests`, - * - `negtests`, - * - `runtests`, - * - `jvmtests`, - * - `residenttests`, - * - `buildmanagertests`, - * - `shootouttests`, - * - `scalaptests`, - * - `scalachecktests`, - * - `specializedtests`, - * - `instrumentedtests`, - * - `presentationtests`, - * - `scripttests`. * * @author Philippe Haller */ class PartestTask extends Task with CompilationPathProperty with ScalaTask { - - def addConfiguredPosTests(input: FileSet) { - posFiles = Some(input) - } - - def addConfiguredNegTests(input: FileSet) { - negFiles = Some(input) - } - - def addConfiguredRunTests(input: FileSet) { - runFiles = Some(input) - } - - def addConfiguredJvmTests(input: FileSet) { - jvmFiles = Some(input) - } - - def addConfiguredResidentTests(input: FileSet) { - residentFiles = Some(input) - } - - def addConfiguredBuildManagerTests(input: FileSet) { - buildManagerFiles = Some(input) - } - - def addConfiguredScalacheckTests(input: FileSet) { - scalacheckFiles = Some(input) - } - - def addConfiguredScriptTests(input: FileSet) { - scriptFiles = Some(input) - } - - def addConfiguredShootoutTests(input: FileSet) { - shootoutFiles = Some(input) - } - - def addConfiguredScalapTests(input: FileSet) { - scalapFiles = Some(input) - } - - def addConfiguredSpecializedTests(input: FileSet) { - specializedFiles = Some(input) - } - - def addConfiguredInstrumentedTests(input: FileSet) { - instrumentedFiles = Some(input) - } - - def addConfiguredPresentationTests(input: FileSet) { - presentationFiles = Some(input) - } - - def addConfiguredAntTests(input: FileSet) { - antFiles = Some(input) - } - + type Path = org.apache.tools.ant.types.Path + + private var kinds: List[String] = Nil + private var classpath: Option[Path] = None + private var debug = false + private var errorOnFailed: Boolean = true + private var jUnitReportDir: Option[File] = None + private var javaccmd: Option[File] = None + private var javacmd: Option[File] = Option(sys.props("java.home")) map (x => new File(x, "bin/java")) + private var scalacArgs: Option[Seq[Argument]] = None + private var srcDir: Option[String] = None + private var colors: Int = 0 def setSrcDir(input: String) { srcDir = Some(input) } + def setColors(input: String) { + try colors = input.toInt catch { case _: NumberFormatException => () } + if (colors > 0) + sys.props("partest.colors") = colors.toString + } + def setClasspath(input: Path) { if (classpath.isEmpty) classpath = Some(input) @@ -133,15 +74,6 @@ class PartestTask extends Task with CompilationPathProperty with ScalaTask { def setClasspathref(input: Reference) { createClasspath().setRefid(input) } - - def setShowLog(input: Boolean) { - showLog = input - } - - def setShowDiff(input: Boolean) { - showDiff = input - } - def setErrorOnFailed(input: Boolean) { errorOnFailed = input } @@ -150,6 +82,10 @@ class PartestTask extends Task with CompilationPathProperty with ScalaTask { javacmd = Some(input) } + def setKinds(input: String) { + kinds = words(input) + } + def setJavacCmd(input: File) { javaccmd = Some(input) } @@ -165,10 +101,6 @@ class PartestTask extends Task with CompilationPathProperty with ScalaTask { a } - def setTimeout(delay: String) { - timeout = Some(delay) - } - def setDebug(input: Boolean) { debug = input } @@ -177,176 +109,35 @@ class PartestTask extends Task with CompilationPathProperty with ScalaTask { jUnitReportDir = Some(input) } - private var classpath: Option[Path] = None - private var srcDir: Option[String] = None - private var javacmd: Option[File] = None - private var javaccmd: Option[File] = None - private var showDiff: Boolean = false - private var showLog: Boolean = false - private var runFailed: Boolean = false - private var posFiles: Option[FileSet] = None - private var negFiles: Option[FileSet] = None - private var runFiles: Option[FileSet] = None - private var jvmFiles: Option[FileSet] = None - private var residentFiles: Option[FileSet] = None - private var buildManagerFiles: Option[FileSet] = None - private var scalacheckFiles: Option[FileSet] = None - private var scriptFiles: Option[FileSet] = None - private var shootoutFiles: Option[FileSet] = None - private var scalapFiles: Option[FileSet] = None - private var specializedFiles: Option[FileSet] = None - private var instrumentedFiles: Option[FileSet] = None - private var presentationFiles: Option[FileSet] = None - private var antFiles: Option[FileSet] = None - private var errorOnFailed: Boolean = false - private var scalacArgs: Option[Seq[Argument]] = None - private var timeout: Option[String] = None - private var jUnitReportDir: Option[File] = None - private var debug = false - - def fileSetToDir(fs: FileSet) = Directory(fs getDir getProject) - def fileSetToArray(fs: FileSet): Array[SPath] = { - val root = fileSetToDir(fs) - (fs getDirectoryScanner getProject).getIncludedFiles map (root / _) - } - - private def getFiles(fileSet: Option[FileSet]): Array[File] = fileSet match { - case None => Array() - case Some(fs) => fileSetToArray(fs) filterNot (_ hasExtension "log") map (_.jfile) - } - - private def getFilesAndDirs(fileSet: Option[FileSet]): Array[File] = fileSet match { - case None => Array() - case Some(fs) => - def shouldExclude(name: String) = (name endsWith ".obj") || (name startsWith ".") - // println("----> " + fileSet) - - val fileTests = getFiles(Some(fs)) filterNot (x => shouldExclude(x.getName)) - val dirResult = getDirs(Some(fs)) filterNot (x => shouldExclude(x.getName)) - // println("dirs: " + dirResult.toList) - // println("files: " + fileTests.toList) - - dirResult ++ fileTests - } - - private def getDirs(fileSet: Option[FileSet]): Array[File] = fileSet match { - case None => Array() - case Some(fs) => - def shouldExclude(name: String) = (name endsWith ".obj") || (name startsWith ".") - - val dirTests: Iterator[SPath] = fileSetToDir(fs).dirs filterNot (x => shouldExclude(x.name)) - val dirResult = dirTests.toList.toArray map (_.jfile) - - dirResult - } - - - private def getPosFiles = getFilesAndDirs(posFiles) - private def getNegFiles = getFilesAndDirs(negFiles) - private def getRunFiles = getFilesAndDirs(runFiles) - private def getJvmFiles = getFilesAndDirs(jvmFiles) - private def getResidentFiles = getFiles(residentFiles) - private def getBuildManagerFiles = getFilesAndDirs(buildManagerFiles) - private def getScalacheckFiles = getFilesAndDirs(scalacheckFiles) - private def getScriptFiles = getFiles(scriptFiles) - private def getShootoutFiles = getFiles(shootoutFiles) - private def getScalapFiles = getFiles(scalapFiles) - private def getSpecializedFiles = getFiles(specializedFiles) - private def getInstrumentedFiles = getFilesAndDirs(instrumentedFiles) - private def getPresentationFiles = getDirs(presentationFiles) - private def getAntFiles = getFiles(antFiles) - override def execute() { - val opts = getProject().getProperties() get "env.PARTEST_OPTS" - if (opts != null && opts.toString != "") - opts.toString.split(" ") foreach { propDef => - log("setting system property " + propDef) - val kv = propDef split "=" - val key = kv(0) substring 2 - val value = kv(1) - setProp(key, value) - } - - if (isPartestDebug || debug) { - setProp("partest.debug", "true") - nest.NestUI._verbose = true + if (debug || sys.props.contains("partest.debug")) { + nest.NestUI.setDebug() } srcDir foreach (x => setProp("partest.srcdir", x)) val classpath = this.compilationPath getOrElse sys.error("Mandatory attribute 'compilationPath' is not set.") - - val scalaLibrary = { - (classpath.list map { fs => new File(fs) }) find { f => - f.getName match { - case "scala-library.jar" => true - case "library" if (f.getParentFile.getName == "classes") => true - case _ => false - } - } - } getOrElse sys.error("Provided classpath does not contain a Scala library.") - - val scalaReflect = { - (classpath.list map { fs => new File(fs) }) find { f => - f.getName match { - case "scala-reflect.jar" => true - case "reflect" if (f.getParentFile.getName == "classes") => true - case _ => false - } - } - } getOrElse sys.error("Provided classpath does not contain a Scala reflection library.") - - val scalaCompiler = { - (classpath.list map { fs => new File(fs) }) find { f => - f.getName match { - case "scala-compiler.jar" => true - case "compiler" if (f.getParentFile.getName == "classes") => true - case _ => false - } - } - } getOrElse sys.error("Provided classpath does not contain a Scala compiler.") - - val scalaPartest = { - (classpath.list map { fs => new File(fs) }) find { f => - f.getName match { - case "scala-partest.jar" => true - case "partest" if (f.getParentFile.getName == "classes") => true - case _ => false - } - } - } getOrElse sys.error("Provided classpath does not contain a Scala partest.") - - val scalaActors = { - (classpath.list map { fs => new File(fs) }) find { f => - f.getName match { - case "scala-actors.jar" => true - case "actors" if (f.getParentFile.getName == "classes") => true - case _ => false - } - } - } getOrElse sys.error("Provided classpath does not contain a Scala actors.") + val cpfiles = classpath.list map { fs => new File(fs) } toList + def findCp(name: String) = cpfiles find (f => + (f.getName == s"scala-$name.jar") + || (f.absolutePathSegments endsWith Seq("classes", name)) + ) getOrElse sys.error(s"Provided classpath does not contain a Scala $name element.") + + val scalaLibrary = findCp("library") + val scalaReflect = findCp("reflect") + val scalaCompiler = findCp("compiler") + val scalaPartest = findCp("partest") + val scalaActors = findCp("actors") def scalacArgsFlat: Option[Seq[String]] = scalacArgs map (_ flatMap { a => val parts = a.getParts - if(parts eq null) Seq[String]() else parts.toSeq + if (parts eq null) Nil else parts.toSeq }) val antRunner = new scala.tools.partest.nest.AntRunner val antFileManager = antRunner.fileManager - // this is a workaround for https://issues.scala-lang.org/browse/SI-5433 - // when that bug is fixed, this paragraph of code can be safely removed - // we hack into the classloader that will become parent classloader for scalac - // this way we ensure that reflective macro lookup will pick correct Code.lift - val loader = getClass.getClassLoader.asInstanceOf[org.apache.tools.ant.AntClassLoader] - val path = new org.apache.tools.ant.types.Path(getProject()) - val newClassPath = ClassPath.join(nest.PathSettings.srcCodeLib.toString, loader.getClasspath) - path.setPath(newClassPath) - loader.setClassPath(path) - - antFileManager.showDiff = showDiff - antFileManager.showLog = showLog - antFileManager.failed = runFailed + // antFileManager.failed = runFailed antFileManager.CLASSPATH = ClassPath.join(classpath.list: _*) antFileManager.LATEST_LIB = scalaLibrary.getAbsolutePath antFileManager.LATEST_REFLECT = scalaReflect.getAbsolutePath @@ -357,54 +148,35 @@ class PartestTask extends Task with CompilationPathProperty with ScalaTask { javacmd foreach (x => antFileManager.JAVACMD = x.getAbsolutePath) javaccmd foreach (x => antFileManager.JAVAC_CMD = x.getAbsolutePath) scalacArgsFlat foreach (antFileManager.SCALAC_OPTS ++= _) - timeout foreach (antFileManager.timeout = _) - - type TFSet = (Array[File], String, String) - val testFileSets = List( - (getPosFiles, "pos", "Compiling files that are expected to build"), - (getNegFiles, "neg", "Compiling files that are expected to fail"), - (getRunFiles, "run", "Compiling and running files"), - (getJvmFiles, "jvm", "Compiling and running files"), - (getResidentFiles, "res", "Running resident compiler scenarii"), - (getBuildManagerFiles, "buildmanager", "Running Build Manager scenarii"), - (getScalacheckFiles, "scalacheck", "Running scalacheck tests"), - (getScriptFiles, "script", "Running script files"), - (getShootoutFiles, "shootout", "Running shootout tests"), - (getScalapFiles, "scalap", "Running scalap tests"), - (getSpecializedFiles, "specialized", "Running specialized files"), - (getInstrumentedFiles, "instrumented", "Running instrumented files"), - (getPresentationFiles, "presentation", "Running presentation compiler test files"), - (getAntFiles, "ant", "Running ant task tests") - ) - - def runSet(set: TFSet): (Int, Int, Iterable[String]) = { - val (files, name, msg) = set + + def runSet(kind: String, files: Array[File]): (Int, Int, List[String]) = { if (files.isEmpty) (0, 0, List()) else { - log(msg) - val results: Iterable[(String, TestState)] = antRunner.reflectiveRunTestsForFiles(files, name) - val (succs, fails) = resultsToStatistics(results) + log(s"Running ${files.length} tests in '$kind' at $now") + // log(s"Tests: ${files.toList}") + val results = antRunner.reflectiveRunTestsForFiles(files, kind) + val (passed, failed) = results partition (_.isOk) + val numPassed = passed.size + val numFailed = failed.size + def failedMessages = failed map (_.longStatus) - val failed: Iterable[String] = results collect { - case (path, TestState.Fail) => path + " [FAILED]" - case (path, TestState.Timeout) => path + " [TIMOUT]" - } + log(s"Completed '$kind' at $now") // create JUnit Report xml files if directory was specified jUnitReportDir foreach { d => d.mkdir - val report = testReport(name, results, succs, fails) - scala.xml.XML.save(d.getAbsolutePath+"/"+name+".xml", report) + val report = testReport(kind, results, numPassed, numFailed) + scala.xml.XML.save(d.getAbsolutePath+"/"+kind+".xml", report) } - (succs, fails, failed) + (numPassed, numFailed, failedMessages) } } - val _results = testFileSets map runSet - val allSuccesses = _results map (_._1) sum - val allFailures = _results map (_._2) sum + val _results = kinds map (k => runSet(k, TestKinds testsFor k map (_.jfile) toArray)) + val allSuccesses = _results map (_._1) sum + val allFailures = _results map (_._2) sum val allFailedPaths = _results flatMap (_._3) def f = if (errorOnFailed && allFailures > 0) buildError(_: String) else log(_: String) @@ -419,20 +191,17 @@ class PartestTask extends Task with CompilationPathProperty with ScalaTask { f(msg) } - private def oneResult(res: (String, TestState)) = - <testcase name={res._1}>{ - res._2 match { - case TestState.Ok => scala.xml.NodeSeq.Empty - case TestState.Fail => <failure message="Test failed"/> - case TestState.Timeout => <failure message="Test timed out"/> - } + private def oneResult(res: TestState) = + <testcase name={res.testIdent}>{ + if (res.isOk) scala.xml.NodeSeq.Empty + else <failure message="Test failed"/> }</testcase> - private def testReport(kind: String, results: Iterable[(String, TestState)], succs: Int, fails: Int) = + private def testReport(kind: String, results: Iterable[TestState], succs: Int, fails: Int) = <testsuite name={kind} tests={(succs + fails).toString} failures={fails.toString}> <properties/> { - results.map(oneResult(_)) + results map oneResult } </testsuite> } diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala deleted file mode 100644 index b9abff69d8..0000000000 --- a/src/partest/scala/tools/partest/ScaladocModelTest.scala +++ /dev/null @@ -1,205 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Vlad Ureche - */ - -package scala.tools.partest - -import scala.tools.partest._ -import java.io._ -import scala.tools.nsc._ -import scala.tools.nsc.util.CommandLineParser -import scala.tools.nsc.doc.{Settings, DocFactory, Universe} -import scala.tools.nsc.doc.model._ -import scala.tools.nsc.doc.model.diagram._ -import scala.tools.nsc.doc.base.comment._ -import scala.tools.nsc.reporters.ConsoleReporter - -/** A class for testing scaladoc model generation - * - you need to specify the code in the `code` method - * - you need to override the testModel method to test the model - * - you may specify extra parameters to send to scaladoc in `scaladocSettings` - * {{{ - import scala.tools.nsc.doc.model._ - import scala.tools.partest.ScaladocModelTest - - object Test extends ScaladocModelTest { - - override def code = """ ... """ // or override def resourceFile = "<file>.scala" (from test/scaladoc/resources) - def scaladocSettings = " ... " - def testModel(rootPackage: Package) = { - // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) - import access._ - - // just need to check the member exists, access methods will throw an error if there's a problem - rootPackage._package("scala")._package("test")._class("C")._method("foo") - } - } - * }}} - */ -abstract class ScaladocModelTest extends DirectTest { - - /** Override this to give scaladoc command line parameters */ - def scaladocSettings: String - - /** Override this to test the model */ - def testModel(root: Package): Unit - - /** Override to feed a file in resources to scaladoc*/ - def resourceFile: String = null - - /** Override to feed code into scaladoc */ - override def code = - if (resourceFile ne null) - io.File(resourcePath + "/" + resourceFile).slurp() - else - sys.error("Scaladoc Model Test: You need to give a file or some code to feed to scaladoc!") - - def resourcePath = io.Directory(sys.props("partest.cwd") + "/../resources") - - // Implementation follows: - override def extraSettings: String = "-usejavacp" - - override def show(): Unit = { - // redirect err to out, for logging - val prevErr = System.err - System.setErr(System.out) - - try { - // 1 - compile with scaladoc and get the model out - val universe = model.getOrElse({sys.error("Scaladoc Model Test ERROR: No universe generated!")}) - // 2 - check the model generated - testModel(universe.rootPackage) - println("Done.") - } catch { - case e: Exception => - println(e) - e.printStackTrace - } - // set err back to the real err handler - System.setErr(prevErr) - } - - private[this] var settings: Settings = null - - // create a new scaladoc compiler - private[this] def newDocFactory: DocFactory = { - settings = new Settings(_ => ()) - settings.scaladocQuietRun = true // yaay, no more "model contains X documentable templates"! - val args = extraSettings + " " + scaladocSettings - val command = new ScalaDoc.Command((CommandLineParser tokenize (args)), settings) - val docFact = new DocFactory(new ConsoleReporter(settings), settings) - docFact - } - - // compile with scaladoc and output the result - def model: Option[Universe] = newDocFactory.makeUniverse(Right(code)) - - // so we don't get the newSettings warning - override def isDebug = false - - - // finally, enable easy navigation inside the entities - object access { - - implicit class TemplateAccess(tpl: DocTemplateEntity) { - def _class(name: String): DocTemplateEntity = getTheFirst(_classes(name), tpl.qualifiedName + ".class(" + name + ")") - def _classes(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case c: DocTemplateEntity with Class => c}) - - def _classMbr(name: String): MemberTemplateEntity = getTheFirst(_classesMbr(name), tpl.qualifiedName + ".classMember(" + name + ")") - def _classesMbr(name: String): List[MemberTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case c: MemberTemplateEntity if c.isClass => c}) - - def _trait(name: String): DocTemplateEntity = getTheFirst(_traits(name), tpl.qualifiedName + ".trait(" + name + ")") - def _traits(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case t: DocTemplateEntity with Trait => t}) - - def _traitMbr(name: String): MemberTemplateEntity = getTheFirst(_traitsMbr(name), tpl.qualifiedName + ".traitMember(" + name + ")") - def _traitsMbr(name: String): List[MemberTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case t: MemberTemplateEntity if t.isTrait => t}) - - def _object(name: String): DocTemplateEntity = getTheFirst(_objects(name), tpl.qualifiedName + ".object(" + name + ")") - def _objects(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case o: DocTemplateEntity with Object => o}) - - def _objectMbr(name: String): MemberTemplateEntity = getTheFirst(_objectsMbr(name), tpl.qualifiedName + ".objectMember(" + name + ")") - def _objectsMbr(name: String): List[MemberTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case o: MemberTemplateEntity if o.isObject => o}) - - def _method(name: String): Def = getTheFirst(_methods(name), tpl.qualifiedName + ".method(" + name + ")") - def _methods(name: String): List[Def] = tpl.methods.filter(_.name == name) - - def _value(name: String): Val = getTheFirst(_values(name), tpl.qualifiedName + ".value(" + name + ")") - def _values(name: String): List[Val] = tpl.values.filter(_.name == name) - - def _conversion(name: String): ImplicitConversion = getTheFirst(_conversions(name), tpl.qualifiedName + ".conversion(" + name + ")") - def _conversions(name: String): List[ImplicitConversion] = tpl.conversions.filter(_.conversionQualifiedName == name) - - def _absType(name: String): MemberEntity = getTheFirst(_absTypes(name), tpl.qualifiedName + ".abstractType(" + name + ")") - def _absTypes(name: String): List[MemberEntity] = tpl.members.filter(mbr => mbr.name == name && mbr.isAbstractType) - - def _absTypeTpl(name: String): DocTemplateEntity = getTheFirst(_absTypeTpls(name), tpl.qualifiedName + ".abstractType(" + name + ")") - def _absTypeTpls(name: String): List[DocTemplateEntity] = tpl.members.collect({ case dtpl: DocTemplateEntity with AbstractType if dtpl.name == name => dtpl }) - - def _aliasType(name: String): MemberEntity = getTheFirst(_aliasTypes(name), tpl.qualifiedName + ".aliasType(" + name + ")") - def _aliasTypes(name: String): List[MemberEntity] = tpl.members.filter(mbr => mbr.name == name && mbr.isAliasType) - - def _aliasTypeTpl(name: String): DocTemplateEntity = getTheFirst(_aliasTypeTpls(name), tpl.qualifiedName + ".aliasType(" + name + ")") - def _aliasTypeTpls(name: String): List[DocTemplateEntity] = tpl.members.collect({ case dtpl: DocTemplateEntity with AliasType if dtpl.name == name => dtpl }) - } - - trait WithMembers { - def members: List[MemberEntity] - def _member(name: String): MemberEntity = getTheFirst(_members(name), this.toString + ".member(" + name + ")") - def _members(name: String): List[MemberEntity] = members.filter(_.name == name) - } - implicit class PackageAccess(pack: Package) extends TemplateAccess(pack) { - def _package(name: String): Package = getTheFirst(_packages(name), pack.qualifiedName + ".package(" + name + ")") - def _packages(name: String): List[Package] = pack.packages.filter(_.name == name) - } - implicit class DocTemplateEntityMembers(val underlying: DocTemplateEntity) extends WithMembers { - def members = underlying.members - } - implicit class ImplicitConversionMembers(val underlying: ImplicitConversion) extends WithMembers { - def members = underlying.members - } - - def getTheFirst[T](list: List[T], expl: String): T = list.length match { - case 1 => list.head - case 0 => sys.error("Error getting " + expl + ": No such element.") - case _ => sys.error("Error getting " + expl + ": " + list.length + " elements with this name. " + - "All elements in list: [" + list.map({ - case ent: Entity => ent.kind + " " + ent.qualifiedName - case other => other.toString - }).mkString(", ") + "]") - } - - def extractCommentText(c: Any) = { - def extractText(body: Any): String = body match { - case s: String => s - case s: Seq[_] => s.toList.map(extractText(_)).mkString - case p: Product => p.productIterator.toList.map(extractText(_)).mkString - case _ => "" - } - c match { - case c: Comment => - extractText(c.body) - case b: Body => - extractText(b) - } - } - - def countLinks(c: Comment, p: EntityLink => Boolean) = { - def countLinks(body: Any): Int = body match { - case el: EntityLink if p(el) => 1 - case s: Seq[_] => s.toList.map(countLinks(_)).sum - case p: Product => p.productIterator.toList.map(countLinks(_)).sum - case _ => 0 - } - countLinks(c.body) - } - - def testDiagram(doc: DocTemplateEntity, diag: Option[Diagram], nodes: Int, edges: Int) = { - assert(diag.isDefined, doc.qualifiedName + " diagram missing") - assert(diag.get.nodes.length == nodes, - doc.qualifiedName + "'s diagram: node count " + diag.get.nodes.length + " == " + nodes) - assert(diag.get.edges.map(_._2.length).sum == edges, - doc.qualifiedName + "'s diagram: edge count " + diag.get.edges.length + " == " + edges) - } - } -} diff --git a/src/partest/scala/tools/partest/SecurityTest.scala b/src/partest/scala/tools/partest/SecurityTest.scala index 2d6f61d0b1..1f1c8a95ea 100644 --- a/src/partest/scala/tools/partest/SecurityTest.scala +++ b/src/partest/scala/tools/partest/SecurityTest.scala @@ -10,23 +10,10 @@ import java.util._ abstract class SecurityTest extends App { def throwIt(x: Any) = throw new AccessControlException("" + x) - - def readPerm(p: PropertyPermission) = p.getActions contains "read" - def writePerm(p: PropertyPermission) = p.getActions contains "write" def propertyCheck(p: PropertyPermission): Unit = throwIt(p) def check(perm: Permission): Unit = perm match { case p: PropertyPermission => propertyCheck(p) case _ => () } - - lazy val sm = new SecurityManager { - // these two are the choke points for all permissions checks - override def checkPermission(perm: Permission): Unit = check(perm) - override def checkPermission(perm: Permission, context: Object): Unit = check(perm) - } - def securityOn(): Boolean = { - try { System.setSecurityManager(sm) ; true } - catch { case _: SecurityException => false } - } } diff --git a/src/partest/scala/tools/partest/TestKinds.scala b/src/partest/scala/tools/partest/TestKinds.scala new file mode 100644 index 0000000000..ec682690ca --- /dev/null +++ b/src/partest/scala/tools/partest/TestKinds.scala @@ -0,0 +1,67 @@ +package scala.tools +package partest + +import nest.PathSettings.srcDir + +object TestKinds { + val standardKinds = "pos neg run jvm res buildmanager scalacheck scalap specialized instrumented presentation ant" split "\\s+" toList + val standardArgs = standardKinds map ("--" + _) + + def denotesTestFile(p: Path) = p.isFile && p.hasExtension("scala", "res", "xml") + def denotesTestDir(p: Path) = kindOf(p) match { + case "res" => false + case _ => p.isDirectory && p.extension == "" + } + def denotesTestPath(p: Path) = denotesTestDir(p) || denotesTestFile(p) + + // TODO + def isTestForPartest(p: Path) = ( + (p.name == "intentional-failure.scala") + || (p.path contains "test-for-partest") + ) + + def kindOf(p: Path) = { + p.toAbsolute.segments takeRight 2 head + + // (srcDir relativize p.toCanonical).segments match { + // case (".." :: "scaladoc" :: xs) => xs.head + // case xs => xs.head + // } + } + def logOf(p: Path) = { + p.parent / s"${p.stripExtension}-${kindOf(p)}.log" + // p.parent / s"${p.stripExtension}.log" + } + + // true if a test path matches the --grep expression. + private def pathMatchesExpr(path: Path, expr: String) = { + // Matches the expression if any source file contains the expr, + // or if the checkfile contains it, or if the filename contains + // it (the last is case-insensitive.) + def matches(p: Path) = ( + (p.path.toLowerCase contains expr.toLowerCase) + || (p.fileContents contains expr) + ) + def candidates = { + (path changeExtension "check") +: { + if (path.isFile) List(path) + else path.toDirectory.deepList() filter (_.isJavaOrScala) toList + } + } + + (candidates exists matches) + } + + def groupedTests(paths: List[Path]): List[(String, List[Path])] = + (paths.distinct groupBy kindOf).toList sortBy (standardKinds indexOf _._1) + + /** Includes tests for testing partest. */ + private def allTestsForKind(kind: String): List[Path] = + (srcDir / kind toDirectory).list.toList filter denotesTestPath + + def testsForPartest: List[Path] = standardKinds flatMap allTestsForKind filter isTestForPartest + def testsFor(kind: String): List[Path] = allTestsForKind(kind) filterNot isTestForPartest + def grepFor(expr: String): List[Path] = standardTests filter (t => pathMatchesExpr(t, expr)) + def standardTests: List[Path] = standardKinds flatMap testsFor + def failedTests: List[Path] = standardTests filter (p => logOf(p).isFile) +} diff --git a/src/partest/scala/tools/partest/TestState.scala b/src/partest/scala/tools/partest/TestState.scala new file mode 100644 index 0000000000..dbe8a222a5 --- /dev/null +++ b/src/partest/scala/tools/partest/TestState.scala @@ -0,0 +1,65 @@ +package scala.tools.partest + +import scala.tools.nsc.FatalError +import scala.tools.nsc.util.stackTraceString + +sealed abstract class TestState { + def testFile: File + def what: String + def reason: String + def transcript: List[String] + + def isOk = false + def isSkipped = false + def testIdent = testFile.testIdent + def transcriptString = transcript mkString EOL + + def identAndReason = testIdent + reasonString + def status = s"$what - $identAndReason" + def longStatus = status + transcriptString + def reasonString = if (reason == "") "" else s" [$reason]" + + def shortStatus = if (isOk) "ok" else "!!" + + override def toString = status +} + +object TestState { + case class Uninitialized(testFile: File) extends TestState { + def what = "uninitialized" + def reason = what + def transcript = Nil + override def shortStatus = "??" + } + case class Pass(testFile: File) extends TestState { + def what = "pass" + override def isOk = true + def transcript: List[String] = Nil + def reason = "" + } + case class Updated(testFile: File) extends TestState { + def what = "updated" + override def isOk = true + def transcript: List[String] = Nil + def reason = "updated check file" + override def shortStatus = "++" + } + case class Skip(testFile: File, reason: String) extends TestState { + def what = "skip" + override def isOk = true + override def isSkipped = true + def transcript: List[String] = Nil + override def shortStatus = "--" + } + case class Fail(testFile: File, reason: String, transcript: List[String]) extends TestState { + def what = "fail" + } + case class Crash(testFile: File, caught: Throwable, transcript: List[String]) extends TestState { + def what = "crash" + def reason = s"caught $caught_s - ${caught.getMessage}" + + private def caught_s = (caught.getClass.getName split '.').last + private def stack_s = stackTraceString(caught) + override def transcriptString = nljoin(super.transcriptString, caught_s) + } +} diff --git a/src/partest/scala/tools/partest/TestUtil.scala b/src/partest/scala/tools/partest/TestUtil.scala index 9bfd444180..5c177ac962 100644 --- a/src/partest/scala/tools/partest/TestUtil.scala +++ b/src/partest/scala/tools/partest/TestUtil.scala @@ -24,14 +24,6 @@ trait TestUtil { } def nanos(body: => Unit): Long = alsoNanos(body)._1 - def verifySpeed(body1: => Unit, body2: => Unit, acceptableMultiple: Double) = { - val t1 = nanos(body1).toDouble - val t2 = nanos(body2).toDouble - val mult = if (t1 > t2) t1 / t2 else t2 / t1 - - assert(mult <= acceptableMultiple, "Performance difference too great: multiple = " + mult) - } - def intercept[T <: Exception : ClassTag](code: => Unit): Unit = try { code @@ -41,6 +33,6 @@ trait TestUtil { } } +// Used in tests. object TestUtil extends TestUtil { - } diff --git a/src/partest/scala/tools/partest/instrumented/Instrumentation.scala b/src/partest/scala/tools/partest/instrumented/Instrumentation.scala index 8a284b313b..18dd740208 100644 --- a/src/partest/scala/tools/partest/instrumented/Instrumentation.scala +++ b/src/partest/scala/tools/partest/instrumented/Instrumentation.scala @@ -78,6 +78,7 @@ object Instrumentation { !t.className.startsWith("scala/util/DynamicVariable") } + // Used in tests. def printStatistics(stats: Statistics = getStatistics, filter: MethodCallTrace => Boolean = standardFilter): Unit = { val stats = getStatistics println("Method call statistics:") diff --git a/src/partest/scala/tools/partest/nest/AntRunner.scala b/src/partest/scala/tools/partest/nest/AntRunner.scala index 93045b8c1d..1d3b79171b 100644 --- a/src/partest/scala/tools/partest/nest/AntRunner.scala +++ b/src/partest/scala/tools/partest/nest/AntRunner.scala @@ -10,24 +10,21 @@ package scala.tools.partest package nest -import java.io.File -import scala.tools.nsc.io.{ Directory } - class AntRunner extends DirectRunner { val fileManager = new FileManager { - var JAVACMD: String = "java" - var JAVAC_CMD: String = "javac" - var CLASSPATH: String = _ - var LATEST_LIB: String = _ - var LATEST_REFLECT: String = _ - var LATEST_COMP: String = _ - var LATEST_PARTEST: String = _ - var LATEST_ACTORS: String = _ - val testRootPath: String = "test" - val testRootDir: Directory = Directory(testRootPath) + var JAVACMD: String = "java" + var JAVAC_CMD: String = "javac" + var CLASSPATH: String = _ + var LATEST_LIB: String = _ + var LATEST_REFLECT: String = _ + var LATEST_COMP: String = _ + var LATEST_PARTEST: String = _ + var LATEST_ACTORS: String = _ + val testRootPath: String = "test" + val testRootDir: Directory = Directory(testRootPath) } - def reflectiveRunTestsForFiles(kindFiles: Array[File], kind: String) = + def reflectiveRunTestsForFiles(kindFiles: Array[File], kind: String): List[TestState] = runTestsForFiles(kindFiles.toList, kind) } diff --git a/src/partest/scala/tools/partest/nest/CompileManager.scala b/src/partest/scala/tools/partest/nest/CompileManager.scala deleted file mode 100644 index 3d902d6d00..0000000000 --- a/src/partest/scala/tools/partest/nest/CompileManager.scala +++ /dev/null @@ -1,164 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2013 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -import scala.tools.nsc.{ Global, Settings, CompilerCommand, FatalError, io } -import scala.tools.nsc.io.{ File => SFile } -import scala.tools.nsc.interactive.RangePositions -import scala.tools.nsc.reporters.{ Reporter, ConsoleReporter } -import scala.tools.nsc.util.{ ClassPath, FakePos } -import scala.tools.nsc.Properties.{ setProp, propOrEmpty } -import scala.tools.util.PathResolver -import io.Path -import java.io.{ File, BufferedReader, PrintWriter, FileReader, Writer, FileWriter, StringWriter } -import File.pathSeparator - -sealed abstract class CompilationOutcome { - def merge(other: CompilationOutcome): CompilationOutcome - def isPositive = this eq CompileSuccess - def isNegative = this eq CompileFailed -} -case object CompileSuccess extends CompilationOutcome { - def merge(other: CompilationOutcome) = other -} -case object CompileFailed extends CompilationOutcome { - def merge(other: CompilationOutcome) = if (other eq CompileSuccess) this else other -} -case object CompilerCrashed extends CompilationOutcome { - def merge(other: CompilationOutcome) = this -} - -class ExtConsoleReporter(settings: Settings, val writer: PrintWriter) extends ConsoleReporter(settings, Console.in, writer) { - shortname = true -} - -class TestSettings(cp: String, error: String => Unit) extends Settings(error) { - def this(cp: String) = this(cp, _ => ()) - - nowarnings.value = false - encoding.value = "UTF-8" - classpath.value = cp -} - -abstract class SimpleCompiler { - def compile(out: Option[File], files: List[File], kind: String, log: File): CompilationOutcome -} - -class DirectCompiler(val fileManager: FileManager) extends SimpleCompiler { - def newGlobal(settings: Settings, reporter: Reporter): Global = - if (settings.Yrangepos.value) - new Global(settings, reporter) with RangePositions - else - new Global(settings, reporter) - - def newGlobal(settings: Settings, logWriter: FileWriter): Global = - newGlobal(settings, new ExtConsoleReporter(settings, new PrintWriter(logWriter))) - - def newSettings(): TestSettings = new TestSettings(fileManager.LATEST_LIB) - def newSettings(outdir: String): TestSettings = { - val cp = ClassPath.join(fileManager.LATEST_LIB, outdir) - val s = new TestSettings(cp) - s.outdir.value = outdir - - s - } - - private def updatePluginPath(options: String): String = { - val dir = fileManager.testRootDir - def absolutize(path: String) = Path(path) match { - case x if x.isAbsolute => x.path - case x => (fileManager.testRootDir / x).toAbsolute.path - } - - val (opt1, opt2) = (options split "\\s").toList partition (_ startsWith "-Xplugin:") - val plugins = opt1 map (_ stripPrefix "-Xplugin:") flatMap (_ split pathSeparator) map absolutize - val pluginOption = if (opt1.isEmpty) Nil else List("-Xplugin:" + (plugins mkString pathSeparator)) - - (opt2 ::: pluginOption) mkString " " - } - - def compile(out: Option[File], files: List[File], kind: String, log: File): CompilationOutcome = { - val testSettings = out match { - case Some(f) => newSettings(f.getAbsolutePath) - case _ => newSettings() - } - val logWriter = new FileWriter(log) - - // check whether there is a ".flags" file - val logFile = basename(log.getName) - val flagsFileName = "%s.flags" format (logFile.substring(0, logFile.lastIndexOf("-"))) - val argString = (io.File(log).parent / flagsFileName) ifFile (x => updatePluginPath(x.slurp())) getOrElse "" - - // slurp local flags (e.g., "A_1.flags") - val fstFile = SFile(files(0)) - def isInGroup(num: Int) = fstFile.stripExtension endsWith ("_" + num) - val inGroup = (1 to 9) flatMap (group => if (isInGroup(group)) List(group) else List()) - val localFlagsList = if (inGroup.nonEmpty) { - val localArgString = (fstFile.parent / (fstFile.stripExtension + ".flags")) ifFile (x => updatePluginPath(x.slurp())) getOrElse "" - localArgString.split(' ').toList.filter(_.length > 0) - } else List() - - val allOpts = fileManager.SCALAC_OPTS.toList ::: argString.split(' ').toList.filter(_.length > 0) ::: localFlagsList - val args = allOpts.toList - - NestUI.verbose("scalac options: "+allOpts) - - val command = new CompilerCommand(args, testSettings) - val global = newGlobal(command.settings, logWriter) - val testRep: ExtConsoleReporter = global.reporter.asInstanceOf[ExtConsoleReporter] - - val testFileFn: (File, FileManager) => TestFile = kind match { - case "pos" => PosTestFile.apply - case "neg" => NegTestFile.apply - case "run" => RunTestFile.apply - case "jvm" => JvmTestFile.apply - case "shootout" => ShootoutTestFile.apply - case "scalap" => ScalapTestFile.apply - case "scalacheck" => ScalaCheckTestFile.apply - case "specialized" => SpecializedTestFile.apply - case "instrumented" => InstrumentedTestFile.apply - case "presentation" => PresentationTestFile.apply - case "ant" => AntTestFile.apply - } - val test: TestFile = testFileFn(files.head, fileManager) - if (!test.defineSettings(command.settings, out.isEmpty)) { - testRep.error(FakePos("partest"), test.flags match { - case Some(flags) => "bad flags: " + flags - case _ => "bad settings: " + command.settings - }) - } - - val toCompile = files map (_.getPath) - - try { - NestUI.verbose("compiling "+toCompile) - NestUI.verbose("with classpath: "+global.classPath.toString) - NestUI.verbose("and java classpath: "+ propOrEmpty("java.class.path")) - try new global.Run compile toCompile - catch { - case FatalError(msg) => - testRep.error(null, "fatal error: " + msg) - return CompilerCrashed - } - - testRep.printSummary() - testRep.writer.close() - } - finally logWriter.close() - - if (testRep.hasErrors) CompileFailed - else CompileSuccess - } -} - -class CompileManager(val fileManager: FileManager) { - private def newCompiler = new DirectCompiler(fileManager) - def attemptCompile(outdir: Option[File], sources: List[File], kind: String, log: File): CompilationOutcome = - newCompiler.compile(outdir, sources, kind, log) -} diff --git a/src/partest/scala/tools/partest/nest/ConsoleFileManager.scala b/src/partest/scala/tools/partest/nest/ConsoleFileManager.scala index 08e709de90..b436675d3a 100644 --- a/src/partest/scala/tools/partest/nest/ConsoleFileManager.scala +++ b/src/partest/scala/tools/partest/nest/ConsoleFileManager.scala @@ -3,23 +3,16 @@ * @author Philipp Haller */ -// $Id$ + package scala.tools.partest package nest -import java.io.{ File, FilenameFilter, IOException, StringWriter } +import java.io.{ FilenameFilter, IOException } import java.net.URI import scala.util.Properties.{ propOrElse, scalaCmd, scalacCmd } -import scala.tools.util.PathResolver import scala.tools.nsc.{ io, util } -import util.{ ClassPath } -import io.{ Path, Directory } -import File.pathSeparator -import ClassPath.{ join } import PathResolver.{ Environment, Defaults } -import RunnerUtils._ - class ConsoleFileManager extends FileManager { var testBuild: Option[String] = PartestDefaults.testBuild @@ -57,7 +50,7 @@ class ConsoleFileManager extends FileManager { var JAVAC_CMD = PartestDefaults.javacCmd - NestUI.verbose("CLASSPATH: "+CLASSPATH) + vlog("CLASSPATH: "+CLASSPATH) if (!srcDir.isDirectory) { NestUI.failure("Source directory \"" + srcDir.path + "\" not found") @@ -72,38 +65,33 @@ class ConsoleFileManager extends FileManager { } def findLatest() { - NestUI.verbose("test parent: "+testParent) + vlog("test parent: "+testParent) - def prefixFileWith(parent: File, relPath: String) = (io.File(parent) / relPath).toCanonical + def prefixFileWith(parent: File, relPath: String) = (SFile(parent) / relPath).toCanonical def prefixFile(relPath: String) = (testParent / relPath).toCanonical if (!testClasses.isEmpty) { testClassesDir = Path(testClasses.get).toCanonical.toDirectory - NestUI.verbose("Running with classes in "+testClassesDir) + vlog("Running with classes in "+testClassesDir) - latestFile = testClassesDir.parent / "bin" latestLibFile = testClassesDir / "library" latestActorsFile = testClassesDir / "library" / "actors" latestReflectFile = testClassesDir / "reflect" latestCompFile = testClassesDir / "compiler" latestPartestFile = testClassesDir / "partest" - latestFjbgFile = testParent / "lib" / "fjbg.jar" } else if (testBuild.isDefined) { val dir = Path(testBuild.get) - NestUI.verbose("Running on "+dir) - latestFile = dir / "bin" + vlog("Running on "+dir) latestLibFile = dir / "lib/scala-library.jar" latestActorsFile = dir / "lib/scala-actors.jar" latestReflectFile = dir / "lib/scala-reflect.jar" latestCompFile = dir / "lib/scala-compiler.jar" latestPartestFile = dir / "lib/scala-partest.jar" - latestFjbgFile = testParent / "lib" / "fjbg.jar" } else { def setupQuick() { - NestUI.verbose("Running build/quick") - latestFile = prefixFile("build/quick/bin") + vlog("Running build/quick") latestLibFile = prefixFile("build/quick/classes/library") latestActorsFile = prefixFile("build/quick/classes/library/actors") latestReflectFile = prefixFile("build/quick/classes/reflect") @@ -112,9 +100,8 @@ class ConsoleFileManager extends FileManager { } def setupInst() { - NestUI.verbose("Running dist (installed)") + vlog("Running dist (installed)") val p = testParent.getParentFile - latestFile = prefixFileWith(p, "bin") latestLibFile = prefixFileWith(p, "lib/scala-library.jar") latestActorsFile = prefixFileWith(p, "lib/scala-actors.jar") latestReflectFile = prefixFileWith(p, "lib/scala-reflect.jar") @@ -123,8 +110,7 @@ class ConsoleFileManager extends FileManager { } def setupDist() { - NestUI.verbose("Running dists/latest") - latestFile = prefixFile("dists/latest/bin") + vlog("Running dists/latest") latestLibFile = prefixFile("dists/latest/lib/scala-library.jar") latestActorsFile = prefixFile("dists/latest/lib/scala-actors.jar") latestReflectFile = prefixFile("dists/latest/lib/scala-reflect.jar") @@ -133,8 +119,7 @@ class ConsoleFileManager extends FileManager { } def setupPack() { - NestUI.verbose("Running build/pack") - latestFile = prefixFile("build/pack/bin") + vlog("Running build/pack") latestLibFile = prefixFile("build/pack/lib/scala-library.jar") latestActorsFile = prefixFile("build/pack/lib/scala-actors.jar") latestReflectFile = prefixFile("build/pack/lib/scala-reflect.jar") @@ -142,11 +127,6 @@ class ConsoleFileManager extends FileManager { latestPartestFile = prefixFile("build/pack/lib/scala-partest.jar") } - val dists = testParent / "dists" - val build = testParent / "build" - // in case of an installed dist, testRootDir is one level deeper - val bin = testParent.parent / "bin" - def mostRecentOf(base: String, names: String*) = names map (x => prefixFile(base + "/" + x).lastModified) reduceLeft (_ max _) @@ -165,8 +145,6 @@ class ConsoleFileManager extends FileManager { // run setup based on most recent time pairs(pairs.keys max)() - - latestFjbgFile = prefixFile("lib/fjbg.jar") } LATEST_LIB = latestLibFile.getAbsolutePath @@ -182,20 +160,18 @@ class ConsoleFileManager extends FileManager { var LATEST_PARTEST: String = "" var LATEST_ACTORS: String = "" - var latestFile: File = _ var latestLibFile: File = _ var latestActorsFile: File = _ var latestReflectFile: File = _ var latestCompFile: File = _ var latestPartestFile: File = _ - var latestFjbgFile: File = _ - def latestScalapFile: File = (latestLibFile.parent / "scalap.jar").jfile + //def latestScalapFile: File = (latestLibFile.parent / "scalap.jar").jfile + //def latestScalapFile: File = new File(latestLibFile.getParentFile, "scalap.jar") var testClassesDir: Directory = _ // initialize above fields findLatest() - var testFiles: List[io.Path] = Nil - + /* def getFiles(kind: String, cond: Path => Boolean): List[File] = { def ignoreDir(p: Path) = List("svn", "obj") exists (p hasExtension _) @@ -204,10 +180,10 @@ class ConsoleFileManager extends FileManager { if (dir.isDirectory) NestUI.verbose("look in %s for tests" format dir) else NestUI.failure("Directory '%s' not found" format dir) - val files = - if (testFiles.nonEmpty) testFiles filter (_.parent isSame dir) - else dir.list filterNot ignoreDir filter cond toList + val files = dir.list filterNot ignoreDir filter cond toList ( if (failed) files filter (x => logFileExists(x, kind)) else files ) map (_.jfile) } + */ + var latestFjbgFile: File = _ } diff --git a/src/partest/scala/tools/partest/nest/ConsoleRunner.scala b/src/partest/scala/tools/partest/nest/ConsoleRunner.scala index e016fb7c92..33bf836a7b 100644 --- a/src/partest/scala/tools/partest/nest/ConsoleRunner.scala +++ b/src/partest/scala/tools/partest/nest/ConsoleRunner.scala @@ -3,90 +3,121 @@ * @author Philipp Haller */ -// $Id$ - -package scala.tools.partest +package scala.tools +package partest package nest -import java.io.{File, PrintStream, FileOutputStream, BufferedReader, - InputStreamReader, StringWriter, PrintWriter} import utils.Properties._ -import RunnerUtils._ import scala.tools.nsc.Properties.{ versionMsg, setProp } -import scala.tools.nsc.util.CommandLineParser -import scala.tools.nsc.io -import io.{ Path } +import scala.tools.nsc.util.CommandLine import scala.collection.{ mutable, immutable } +import PathSettings.srcDir +import TestKinds._ +import scala.reflect.internal.util.Collections.distinctBy class ConsoleRunner extends DirectRunner { - import PathSettings.{ srcDir, testRoot } - - case class TestSet(kind: String, filter: Path => Boolean, msg: String) - private def stdFilter(p: Path) = p.isDirectory || (p hasExtension "scala") - private def antFilter(p: Path) = p.isFile && (p endsWith "build.xml") - - val testSets = { - val pathFilter: Path => Boolean = x => x.isDirectory || (x hasExtension "scala") - - List( - TestSet("pos", stdFilter, "Testing compiler (on files whose compilation should succeed)"), - TestSet("neg", stdFilter, "Testing compiler (on files whose compilation should fail)"), - TestSet("run", stdFilter, "Testing interpreter and backend"), - TestSet("jvm", stdFilter, "Testing JVM backend"), - TestSet("res", x => x.isFile && (x hasExtension "res"), "Testing resident compiler"), - TestSet("buildmanager", _.isDirectory, "Testing Build Manager"), - TestSet("shootout", stdFilter, "Testing shootout tests"), - TestSet("script", stdFilter, "Testing script tests"), - TestSet("scalacheck", stdFilter, "Testing ScalaCheck tests"), - TestSet("scalap", _.isDirectory, "Run scalap decompiler tests"), - TestSet("specialized", stdFilter, "Testing specialized tests"), - TestSet("instrumented", stdFilter, "Testing instrumented tests"), - TestSet("presentation", _.isDirectory, "Testing presentation compiler tests."), - TestSet("ant", antFilter, "Run Ant task tests.") - ) - } + import NestUI._ + import NestUI.color._ + + // So we can ctrl-C a test run and still hear all + // the buffered failure info. + scala.sys addShutdownHook issueSummaryReport() var fileManager: ConsoleFileManager = _ - private var testFiles: List[File] = List() - private val errors = PartestDefaults.errorCount - private val testSetKinds = testSets map (_.kind) - private val testSetArgs = testSets map ("--" + _.kind) - private val testSetArgMap = testSetArgs zip testSets toMap + private var totalTests = 0 + private val passedTests = mutable.ListBuffer[TestState]() + private val failedTests = mutable.ListBuffer[TestState]() + + def comment(s: String) = echo(magenta("# " + s)) + def levyJudgment() = { + if (totalTests == 0) echoMixed("No tests to run.") + else if (elapsedMillis == 0) echoMixed("Test Run ABORTED") + else if (isSuccess) echoPassed("Test Run PASSED") + else echoFailed("Test Run FAILED") + } - def denotesTestSet(arg: String) = testSetArgs contains arg + def passFailString(passed: Int, failed: Int, skipped: Int): String = { + val total = passed + failed + skipped + val isSuccess = failed == 0 + def p0 = s"$passed/$total" + def p = ( if (isSuccess) bold(green(p0)) else p0 ) + " passed" + def f = if (failed == 0) "" else bold(red("" + failed)) + " failed" + def s = if (skipped == 0) "" else bold(yellow("" + skipped)) + " skipped" - private def printVersion() { NestUI outline (versionMsg + "\n") } + oempty(p, f, s) mkString ", " + } + + private var summarizing = false + private var elapsedMillis = 0L + private var expectedFailures = 0 + private def isSuccess = failedTests.size == expectedFailures + + def issueSummaryReport() { + // Don't run twice + if (!summarizing) { + summarizing = true + + val passed0 = passedTests.toList + val failed0 = failedTests.toList + val passed = passed0.size + val failed = failed0.size + val skipped = totalTests - (passed + failed) + val passFail = passFailString(passed, failed, skipped) + val elapsed = if (elapsedMillis > 0) " (elapsed time: " + elapsedString(elapsedMillis) + ")" else "" + val message = passFail + elapsed + + if (failed0.nonEmpty) { + if (isPartestVerbose) { + echo(bold(cyan("##### Transcripts from failed tests #####\n"))) + failed0 foreach { state => + comment("partest " + state.testFile) + echo(state.transcriptString + "\n") + } + } + + def files_s = failed0.map(_.testFile).mkString(""" \""" + "\n ") + echo("# Failed test paths (this command will update checkfiles)") + echo("test/partest --update-check \\\n " + files_s + "\n") + } + + echo(message) + levyJudgment() + } + } private val unaryArgs = List( - "--pack", "--all", "--verbose", "--show-diff", "--show-log", + "--pack", "--all", + "--terse", "--verbose", "--show-diff", "--show-log", "--self-test", "--failed", "--update-check", "--version", "--ansi", "--debug", "--help" - ) ::: testSetArgs + ) ::: standardArgs private val binaryArgs = List( - "--grep", "--srcpath", "--buildpath", "--classpath" + "--grep", "--srcpath", "--buildpath", "--classpath", "--timeout" ) - // true if a test path matches the --grep expression. - private def pathMatchesExpr(path: Path, expr: String) = { - def pred(p: Path) = file2String(p.toFile) contains expr - def srcs = path.toDirectory.deepList() filter (_.hasExtension("scala", "java")) - - (path.isFile && pred(path)) || - (path.isDirectory && srcs.exists(pred)) || - (pred(path changeExtension "check")) - } - def main(argstr: String) { - val parsed = CommandLineParser(argstr) withUnaryArgs unaryArgs withBinaryArgs binaryArgs - val args = onlyValidTestPaths(parsed.residualArgs) - - /** Early return on no args, version, or invalid args */ - if (argstr == "") return NestUI.usage() - if (parsed isSet "--version") return printVersion - if (parsed isSet "--help") return NestUI.usage() + val parsed = (new CommandLine(argstr)) withUnaryArgs unaryArgs withBinaryArgs binaryArgs + + if (parsed isSet "--debug") NestUI.setDebug() + if (parsed isSet "--verbose") NestUI.setVerbose() + if (parsed isSet "--terse") NestUI.setTerse() + if (parsed isSet "--show-diff") NestUI.setDiffOnFail() + + // Early return on no args, version, or invalid args + if (parsed isSet "--version") return echo(versionMsg) + if ((argstr == "") || (parsed isSet "--help")) return NestUI.usage() + + val (individualTests, invalid) = parsed.residualArgs map (p => Path(p)) partition denotesTestPath + if (invalid.nonEmpty) { + if (isPartestVerbose) + invalid foreach (p => echoWarning(s"Discarding invalid test path " + p)) + else if (!isPartestTerse) + echoWarning(s"Discarding ${invalid.size} invalid test paths") + } parsed get "--srcpath" foreach (x => setProp("partest.srcdir", x)) + parsed get "--timeout" foreach (x => setProp("partest.timeout", x)) fileManager = if (parsed isSet "--buildpath") new ConsoleFileManager(parsed("--buildpath")) @@ -94,146 +125,102 @@ class ConsoleRunner extends DirectRunner { else if (parsed isSet "--pack") new ConsoleFileManager("build/pack") else new ConsoleFileManager // auto detection, see ConsoleFileManager.findLatest - def argNarrowsTests(x: String) = denotesTestSet(x) || denotesTestPath(x) - - NestUI._verbose = parsed isSet "--verbose" - fileManager.showDiff = true - // parsed isSet "--show-diff" fileManager.updateCheck = parsed isSet "--update-check" - fileManager.showLog = parsed isSet "--show-log" fileManager.failed = parsed isSet "--failed" - if (parsed isSet "--ansi") NestUI initialize NestUI.MANY - if (parsed isSet "--timeout") fileManager.timeout = parsed("--timeout") - if (parsed isSet "--debug") setProp("partest.debug", "true") + val partestTests = ( + if (parsed isSet "--self-test") TestKinds.testsForPartest + else Nil + ) - def addTestFile(file: File) = { - if (!file.exists) - NestUI.failure("Test file '%s' not found, skipping.\n" format file) - else { - NestUI.verbose("adding test file " + file) - testFiles +:= file - } - } + val grepExpr = parsed get "--grep" getOrElse "" // If --grep is given we suck in every file it matches. - - val grepOption = parsed get "--grep" - val grepPaths = grepOption.toList flatMap { expr => - val subjectDirs = testSetKinds map (srcDir / _ toDirectory) - val testPaths = subjectDirs flatMap (_.files filter stdFilter) - val paths = testPaths filter (p => pathMatchesExpr(p, expr)) - + var grepMessage = "" + val greppedTests = if (grepExpr == "") Nil else { + val paths = grepFor(grepExpr) if (paths.isEmpty) - NestUI.failure("--grep string '%s' matched no tests." format expr) + echoWarning(s"grep string '$grepExpr' matched no tests.\n") - paths map (_.jfile) + paths.sortBy(_.toString) } - val grepMessage = grepOption map (x => "Argument '%s' matched %d test(s)".format(x, grepPaths.size)) getOrElse "" - - grepPaths foreach addTestFile - args foreach (x => addTestFile(new File(x))) - // If no file arguments were given, we assume --all - val enabledTestSets: List[TestSet] = { - val enabledArgs = testSetArgs filter parsed.isSet - - if (args.isEmpty && !(parsed isSet "--grep") && (enabledArgs.isEmpty || (parsed isSet "--all"))) testSets - else enabledArgs map testSetArgMap - } + val isRerun = parsed isSet "--failed" + val rerunTests = if (isRerun) TestKinds.failedTests else Nil + def miscTests = partestTests ++ individualTests ++ greppedTests ++ rerunTests + val givenKinds = standardArgs filter parsed.isSet + val kinds = ( + if (parsed isSet "--all") standardKinds + else if (givenKinds.nonEmpty) givenKinds map (_ stripPrefix "--") + else if (invalid.isEmpty && miscTests.isEmpty && !isRerun) standardKinds // If no kinds, --grep, or individual tests were given, assume --all + else Nil + ) + val kindsTests = kinds flatMap testsFor val dir = if (fileManager.testClasses.isDefined) fileManager.testClassesDir else fileManager.testBuildFile getOrElse { fileManager.latestCompFile.getParentFile.getParentFile.getAbsoluteFile } - val vmBin = javaHome + File.separator + "bin" - val vmName = "%s (build %s, %s)".format(javaVmName, javaVmVersion, javaVmInfo) - val vmOpts = fileManager.JAVA_OPTS - - NestUI.verbose("enabled test sets: " + (enabledTestSets map (_.kind) mkString " ")) - - List( - "Scala compiler classes in: " + dir, - "Scala version is: " + versionMsg, - "Scalac options are: " + fileManager.SCALAC_OPTS, - "Java binaries in: " + vmBin, - "Java runtime is: " + vmName, - "Java options are: " + vmOpts, - "Source directory is: " + srcDir, - "" - ) foreach (x => NestUI verbose (x + "\n")) - - NestUI.verbose("available processors: " + Runtime.getRuntime().availableProcessors()) - - // Dragged down here so it isn't buried under the banner. - if (grepMessage != "") - NestUI.normal(grepMessage + "\n") - - val ((successes, failures), elapsedMillis) = timed(testCheckAll(enabledTestSets)) - val total = successes + failures - - val elapsedSecs = elapsedMillis/1000 - val elapsedMins = elapsedSecs/60 - val elapsedHrs = elapsedMins/60 - val dispMins = elapsedMins - elapsedHrs * 60 - val dispSecs = elapsedSecs - elapsedMins * 60 - - val dispElapsed = { - def form(num: Long) = if (num < 10) "0"+num else ""+num - form(elapsedHrs)+":"+form(dispMins)+":"+form(dispSecs) + def testContributors = { + List( + if (partestTests.isEmpty) "" else "partest self-tests", + if (rerunTests.isEmpty) "" else "previously failed tests", + if (kindsTests.isEmpty) "" else s"${kinds.size} named test categories", + if (greppedTests.isEmpty) "" else s"${greppedTests.size} tests matching '$grepExpr'", + if (individualTests.isEmpty) "" else "specified tests" + ) filterNot (_ == "") mkString ", " } - if (failures == 0) - NestUI.success("All of "+total+" tests were successful (elapsed time: "+dispElapsed+")\n") - else - NestUI.failure(failures+" of "+total+" tests failed (elapsed time: "+dispElapsed+")\n") + def banner = { + val vmBin = javaHome + fileSeparator + "bin" + val vmName = "%s (build %s, %s)".format(javaVmName, javaVmVersion, javaVmInfo) + val vmOpts = fileManager.JAVA_OPTS + + s"""|Scala compiler classes in: $dir + |Scala version is: $versionMsg + |Scalac options are: ${fileManager.SCALAC_OPTS mkString " "} + |Java binaries in: $vmBin + |Java runtime is: $vmName + |Java options are: $vmOpts + |Source directory is: $srcDir + |Available processors: ${Runtime.getRuntime().availableProcessors()} + |Java Classpath: ${sys.props("java.class.path")} + """.stripMargin + } - System exit ( if (failures == errors) 0 else 1 ) - } + chatty(banner) - def runTests(testSet: TestSet): (Int, Int) = { - val TestSet(kind, filter, msg) = testSet + val allTests: List[Path] = distinctBy(miscTests ++ kindsTests)(_.toCanonical) sortBy (_.toString) + val grouped = (allTests groupBy kindOf).toList sortBy (x => standardKinds indexOf x._1) - fileManager.getFiles(kind, filter) match { - case Nil => NestUI.verbose("test dir empty\n") ; (0, 0) - case files => - NestUI.verbose("test files: "+files) - NestUI.outline("\n"+msg+"\n") - resultsToStatistics(runTestsForFiles(files, kind)) + totalTests = allTests.size + expectedFailures = propOrNone("partest.errors") match { + case Some(num) => num.toInt + case _ => 0 } - } - - /** - * @return (success count, failure count) - */ - def testCheckAll(enabledSets: List[TestSet]): (Int, Int) = { - def kindOf(f: File) = { - (srcDir relativize Path(f).toCanonical).segments match { - case (".." :: "scaladoc" :: xs) => xs.head - case xs => xs.head + val expectedFailureMessage = if (expectedFailures == 0) "" else s" (expecting $expectedFailures to fail)" + echo(s"Selected $totalTests tests drawn from $testContributors$expectedFailureMessage\n") + + val (_, millis) = timed { + for ((kind, paths) <- grouped) { + val num = paths.size + val ss = if (num == 1) "" else "s" + comment(s"starting $num test$ss in $kind") + val results = runTestsForFiles(paths map (_.jfile), kind) + val (passed, failed) = results partition (_.isOk) + + passedTests ++= passed + failedTests ++= failed + if (failed.nonEmpty) { + comment(passFailString(passed.size, failed.size, 0) + " in " + kind) + } + echo("") } } - - val (valid, invalid) = testFiles partition (x => testSetKinds contains kindOf(x)) - invalid foreach (x => NestUI.failure( - "Invalid test file '%s', skipping.\n".format(x) + - "(Test kind '%s' not in known set '%s')".format(kindOf(x), testSetKinds)) - ) - - val grouped = (valid groupBy kindOf).toList sortBy (x => testSetKinds indexOf x._1) - val runTestsFileLists = - for ((kind, files) <- grouped) yield { - NestUI.outline("\nTesting individual files\n") - resultsToStatistics(runTestsForFiles(files, kind)) - } - - if (enabledSets.nonEmpty) - NestUI.verbose("Run sets: "+enabledSets) - - val results = runTestsFileLists ::: (enabledSets map runTests) - - (results map (_._1) sum, results map (_._2) sum) + this.elapsedMillis = millis + issueSummaryReport() + System exit ( if (isSuccess) 0 else 1 ) } } diff --git a/src/partest/scala/tools/partest/nest/DirectCompiler.scala b/src/partest/scala/tools/partest/nest/DirectCompiler.scala new file mode 100644 index 0000000000..8e5ff2abc4 --- /dev/null +++ b/src/partest/scala/tools/partest/nest/DirectCompiler.scala @@ -0,0 +1,105 @@ +/* NEST (New Scala Test) + * Copyright 2007-2013 LAMP/EPFL + * @author Philipp Haller + */ + +package scala.tools.partest +package nest + +import scala.tools.nsc.{ Global, Settings, CompilerCommand, FatalError } +import scala.tools.nsc.reporters.{ Reporter, ConsoleReporter } +import scala.tools.nsc.util.{ FakePos, stackTraceString } +import scala.tools.nsc.Properties.{ setProp, propOrEmpty } +import scala.reflect.io.AbstractFile +import scala.reflect.internal.util.Position +import java.io.{ BufferedReader, PrintWriter, FileReader, Writer, FileWriter } + +class ExtConsoleReporter(settings: Settings, val writer: PrintWriter) extends ConsoleReporter(settings, Console.in, writer) { + shortname = true + // override def error(pos: Position, msg: String): Unit +} + +class TestSettings(cp: String, error: String => Unit) extends Settings(error) { + def this(cp: String) = this(cp, _ => ()) + + nowarnings.value = false + encoding.value = "UTF-8" + classpath.value = cp +} + +class PartestGlobal(settings: Settings, reporter: Reporter) extends Global(settings, reporter) { + // override def abort(msg: String): Nothing + // override def globalError(msg: String): Unit + // override def supplementErrorMessage(msg: String): String +} +class DirectCompiler(val fileManager: FileManager) { + def newGlobal(settings: Settings, reporter: Reporter): PartestGlobal = + new PartestGlobal(settings, reporter) + + def newGlobal(settings: Settings, logWriter: FileWriter): Global = + newGlobal(settings, new ExtConsoleReporter(settings, new PrintWriter(logWriter))) + + def newSettings(): TestSettings = new TestSettings(fileManager.LATEST_LIB) + def newSettings(outdir: String): TestSettings = { + val cp = ClassPath.join(fileManager.LATEST_LIB, outdir) + val s = new TestSettings(cp) + s.outdir.value = outdir + s + } + + def compile(runner: Runner, opts0: List[String], sources: List[File]): TestState = { + import runner.{ sources => _, _ } + + val testSettings = new TestSettings(ClassPath.join(fileManager.LATEST_LIB, outDir.getPath)) + val logWriter = new FileWriter(logFile) + val srcDir = if (testFile.isDirectory) testFile else Path(testFile).parent.jfile + val opts = fileManager.updatePluginPath(opts0, AbstractFile getDirectory outDir, AbstractFile getDirectory srcDir) + val command = new CompilerCommand(opts, testSettings) + val global = newGlobal(testSettings, logWriter) + val reporter = global.reporter.asInstanceOf[ExtConsoleReporter] + def errorCount = reporter.ERROR.count + + def defineSettings(s: Settings) = { + s.outputDirs setSingleOutput outDir.getPath + // adding codelib.jar to the classpath + // codelib provides the possibility to override standard reify + // this shields the massive amount of reification tests from changes in the API + prependToClasspaths(s, codelib) + s.classpath append fileManager.CLASSPATH // adding this why? + + // add the instrumented library version to classpath + if (kind == "specialized") + prependToClasspaths(s, speclib) + + // check that option processing succeeded + opts0.isEmpty || command.ok + } + + if (!defineSettings(testSettings)) + if (opts0.isEmpty) + reporter.error(null, s"bad settings: $testSettings") + else + reporter.error(null, opts0.mkString("bad options: ", space, "")) + + def ids = sources.map(_.testIdent) mkString space + vlog(s"% scalac $ids") + + def execCompile() = + if (command.shouldStopWithInfo) { + logWriter append (command getInfoMessage global) + runner genFail "compilation stopped with info" + } else { + new global.Run compile sources.map(_.getPath) + if (!reporter.hasErrors) runner.genPass() + else { + reporter.printSummary() + reporter.writer.close() + runner.genFail(s"compilation failed with $errorCount errors") + } + } + + try { execCompile() } + catch { case t: Throwable => reporter.error(null, t.getMessage) ; runner.genCrash(t) } + finally { logWriter.close() } + } +} diff --git a/src/partest/scala/tools/partest/nest/DirectRunner.scala b/src/partest/scala/tools/partest/nest/DirectRunner.scala deleted file mode 100644 index 32ef8b41ea..0000000000 --- a/src/partest/scala/tools/partest/nest/DirectRunner.scala +++ /dev/null @@ -1,75 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2013 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -import java.io.File -import scala.util.Properties.setProp -import scala.tools.nsc.util.ScalaClassLoader -import scala.tools.nsc.io.Path -import scala.collection.{ mutable, immutable } -import java.util.concurrent._ -import scala.collection.convert.decorateAll._ - -case class TestRunParams(val scalaCheckParentClassLoader: ScalaClassLoader) - -trait DirectRunner { - def fileManager: FileManager - - import PartestDefaults.numThreads - - def denotesTestFile(arg: String) = Path(arg).hasExtension("scala", "res", "xml") - def denotesTestDir(arg: String) = Path(arg).ifDirectory(_.files.nonEmpty) exists (x => x) - def denotesTestPath(arg: String) = denotesTestDir(arg) || denotesTestFile(arg) - - /** No duplicate, no empty directories, don't mess with this unless - * you like partest hangs. - */ - def onlyValidTestPaths[T](args: List[T]): List[T] = { - args.distinct filter (arg => denotesTestPath("" + arg) || { - NestUI.warning("Discarding invalid test path '%s'\n" format arg) - false - }) - } - def runTestsForFiles(_kindFiles: List[File], kind: String): immutable.Map[String, TestState] = { - System.setProperty("line.separator", "\n") - - // @partest maintainer: we cannot create a fresh file manager here - // since the FM must respect --buildpath and --classpath from the command line - // for example, see how it's done in ReflectiveRunner - //val consFM = new ConsoleFileManager - //import consFM.{ latestCompFile, latestLibFile, latestPartestFile } - val latestCompFile = new File(fileManager.LATEST_COMP) - val latestReflectFile = new File(fileManager.LATEST_REFLECT) - val latestLibFile = new File(fileManager.LATEST_LIB) - val latestPartestFile = new File(fileManager.LATEST_PARTEST) - val latestActorsFile = new File(fileManager.LATEST_ACTORS) - val scalacheckURL = PathSettings.scalaCheck.toURL - val scalaCheckParentClassLoader = ScalaClassLoader.fromURLs( - scalacheckURL :: (List(latestCompFile, latestReflectFile, latestLibFile, latestActorsFile, latestPartestFile).map(_.toURI.toURL)) - ) - - val kindFiles = onlyValidTestPaths(_kindFiles) - val pool = Executors.newFixedThreadPool(numThreads) - val manager = new RunnerManager(kind, fileManager, TestRunParams(scalaCheckParentClassLoader)) - val futures = kindFiles map (f => (f, pool submit callable(manager runTest f))) toMap - - pool.shutdown() - try if (!pool.awaitTermination(4, TimeUnit.HOURS)) - NestUI.warning("Thread pool timeout elapsed before all tests were complete!") - catch { case t: InterruptedException => - NestUI.warning("Thread pool was interrupted") - t.printStackTrace() - } - - for ((file, future) <- futures) yield { - val state = if (future.isCancelled) TestState.Timeout else future.get - (file.getAbsolutePath, state) - } - } -} diff --git a/src/partest/scala/tools/partest/nest/FileManager.scala b/src/partest/scala/tools/partest/nest/FileManager.scala index 70fdb33c6a..230ada4803 100644 --- a/src/partest/scala/tools/partest/nest/FileManager.scala +++ b/src/partest/scala/tools/partest/nest/FileManager.scala @@ -12,8 +12,7 @@ import java.io.{File, FilenameFilter, IOException, StringWriter, FileInputStream, FileOutputStream, BufferedReader, FileReader, PrintWriter, FileWriter} import java.net.URI -import scala.tools.nsc.io.{ Path, Directory, File => SFile } -import scala.sys.process._ +import scala.reflect.io.AbstractFile import scala.collection.mutable trait FileUtil { @@ -62,6 +61,23 @@ trait FileManager extends FileUtil { var LATEST_PARTEST: String var LATEST_ACTORS: String + protected def relativeToLibrary(what: String): String = { + def jarname = if (what startsWith "scala") s"$what.jar" else s"scala-$what.jar" + if (LATEST_LIB endsWith ".jar") + (SFile(LATEST_LIB).parent / jarname).toAbsolute.path + else + (SFile(LATEST_LIB).parent.parent / "classes" / what).toAbsolute.path + } + def latestScaladoc = relativeToLibrary("scaladoc") + def latestInteractive = relativeToLibrary("interactive") + def latestScalapFile = relativeToLibrary("scalap") + def latestPaths = List( + LATEST_LIB, LATEST_REFLECT, LATEST_COMP, LATEST_PARTEST, LATEST_ACTORS, + latestScalapFile, latestScaladoc, latestInteractive + ) + def latestFiles = latestPaths map (p => new java.io.File(p)) + def latestUrls = latestFiles map (_.toURI.toURL) + var showDiff = false var updateCheck = false var showLog = false @@ -69,18 +85,11 @@ trait FileManager extends FileUtil { var SCALAC_OPTS = PartestDefaults.scalacOpts.split(' ').toSeq var JAVA_OPTS = PartestDefaults.javaOpts - var timeout = PartestDefaults.timeout - // how can 15 minutes not be enough? What are you doing, run/lisp.scala? - // You complete in 11 seconds on my machine. - var oneTestTimeout = 60 * 60 * 1000 /** Only when --debug is given. */ lazy val testTimings = new mutable.HashMap[String, Long] def recordTestTiming(name: String, milliseconds: Long) = synchronized { testTimings(name) = milliseconds } - def showTestTimings() { - testTimings.toList sortBy (-_._2) foreach { case (k, v) => println("%s: %s".format(k, v)) } - } def getLogFile(dir: File, fileBase: String, kind: String): File = new File(dir, fileBase + "-" + kind + ".log") @@ -121,4 +130,34 @@ trait FileManager extends FileUtil { f.printlnAll(f.lines.toList map replace: _*) } + + /** Massage args to merge plugins and fix paths. + * Plugin path can be relative to test root, or cwd is out. + * While we're at it, mix in the baseline options, too. + * That's how ant passes in the plugins dir. + */ + def updatePluginPath(args: List[String], out: AbstractFile, srcdir: AbstractFile): List[String] = { + val dir = testRootDir + // The given path, or the output dir if ".", or a temp dir if output is virtual (since plugin loading doesn't like virtual) + def pathOrCwd(p: String) = + if (p == ".") { + val plugxml = "scalac-plugin.xml" + val pout = if (out.isVirtual) Directory.makeTemp() else Path(out.path) + val srcpath = Path(srcdir.path) + val pd = (srcpath / plugxml).toFile + if (pd.exists) pd copyTo (pout / plugxml) + pout + } else Path(p) + def absolutize(path: String) = pathOrCwd(path) match { + case x if x.isAbsolute => x.path + case x => (dir / x).toAbsolute.path + } + + val xprefix = "-Xplugin:" + val (xplugs, others) = args partition (_ startsWith xprefix) + val Xplugin = if (xplugs.isEmpty) Nil else List(xprefix + + (xplugs map (_ stripPrefix xprefix) flatMap (_ split pathSeparator) map absolutize mkString pathSeparator) + ) + SCALAC_OPTS.toList ::: others ::: Xplugin + } } diff --git a/src/partest/scala/tools/partest/nest/NestUI.scala b/src/partest/scala/tools/partest/nest/NestUI.scala index 70db6d0ed1..564270e531 100644 --- a/src/partest/scala/tools/partest/nest/NestUI.scala +++ b/src/partest/scala/tools/partest/nest/NestUI.scala @@ -3,14 +3,42 @@ * @author Philipp Haller */ -// $Id$ - package scala.tools.partest package nest import java.io.PrintWriter +class Colors(enabled: => Boolean) { + import Console._ + + val bold = colored(BOLD) + val yellow = colored(YELLOW) + val green = colored(GREEN) + val blue = colored(BLUE) + val red = colored(RED) + val red_b = colored(RED_B) + val green_b = colored(GREEN_B) + val cyan = colored(CYAN) + val magenta = colored(MAGENTA) + + private def colored(code: String): String => String = + s => if (enabled) code + s + RESET else s +} + object NestUI { + private val testNum = new java.util.concurrent.atomic.AtomicInteger(1) + @volatile private var testNumberFmt = "%3d" + // @volatile private var testNumber = 1 + private def testNumber = testNumberFmt format testNum.getAndIncrement() + def resetTestNumber(max: Int = -1) { + testNum set 1 + val width = if (max > 0) max.toString.length else 3 + testNumberFmt = s"%${width}d" + } + + var colorEnabled = sys.props contains "partest.colors" + val color = new Colors(colorEnabled) + import color._ val NONE = 0 val SOME = 1 @@ -22,11 +50,66 @@ object NestUI { private var _warning = "" private var _default = "" + private var dotCount = 0 + private val DotWidth = 72 + + def leftFlush() { + if (dotCount != 0) { + normal("\n") + dotCount = 0 + } + } + + def statusLine(state: TestState) = { + import state._ + import TestState._ + val colorizer = state match { + case _: Skip => yellow + case _: Updated => cyan + case s if s.isOk => green + case _ => red + } + val word = bold(colorizer(state.shortStatus)) + f"$word $testNumber - $testIdent%-40s$reasonString" + } + + def reportTest(state: TestState) = { + if (isTerse && state.isOk) { + if (dotCount >= DotWidth) { + outline("\n.") + dotCount = 1 + } + else { + outline(".") + dotCount += 1 + } + } + else { + echo(statusLine(state)) + if (!state.isOk && isDiffy) { + val differ = bold(red("% ")) + "diff " + state.transcript find (_ startsWith differ) foreach (echo(_)) + } + } + } + + def echo(message: String): Unit = synchronized { + leftFlush() + print(message + "\n") + } + def chatty(msg: String) = if (isVerbose) echo(msg) + + def echoSkipped(msg: String) = echo(yellow(msg)) + def echoPassed(msg: String) = echo(bold(green(msg))) + def echoFailed(msg: String) = echo(bold(red(msg))) + def echoMixed(msg: String) = echo(bold(yellow(msg))) + def echoWarning(msg: String) = echo(bold(red(msg))) + def initialize(number: Int) = number match { case MANY => _outline = Console.BOLD + Console.BLACK _success = Console.BOLD + Console.GREEN - _failure = Console.BOLD + Console.RED + _failure = Console.BOLD + Console.RED _warning = Console.BOLD + Console.YELLOW _default = Console.RESET case SOME => @@ -54,9 +137,6 @@ object NestUI { } def warning(msg: String) = print(_warning + msg + _default) - def warning(msg: String, wr: PrintWriter) = synchronized { - wr.print(_warning + msg + _default) - } def normal(msg: String) = print(_default + msg) def normal(msg: String, wr: PrintWriter) = synchronized { @@ -64,10 +144,7 @@ object NestUI { } def usage() { - println("Usage: NestRunner [<options>] [<testfile> ..] [<resfile>]") - println(" <testfile>: list of files ending in '.scala'") - println(" <resfile>: a file not ending in '.scala'") - println(" <options>:") + println("Usage: NestRunner [options] [test test ...]") println println(" Test categories:") println(" --all run all tests") @@ -76,18 +153,13 @@ object NestUI { println(" --run run interpreter and backend tests") println(" --jvm run JVM backend tests") println(" --res run resident compiler tests") - println(" --buildmanager run Build Manager tests") println(" --scalacheck run ScalaCheck tests") - println(" --script run script runner tests") - println(" --shootout run shootout tests") println(" --instrumented run instrumented tests") println(" --presentation run presentation compiler tests") - println(" --grep <expr> run all tests whose source file contains <expr>") println println(" Other options:") println(" --pack pick compiler/reflect/library in build/pack, and run all tests") - println(" --show-log show log") - println(" --show-diff show diff between log and check file") + println(" --grep <expr> run all tests whose source file contains <expr>") println(" --failed run only those tests that failed during the last run") println(" --update-check instead of failing tests with output change, update checkfile. (Use with care!)") println(" --verbose show progress information") @@ -105,17 +177,32 @@ object NestUI { var _verbose = false var _debug = false + var _terse = false + var _diff = false + def isVerbose = _verbose + def isDebug = _debug + def isTerse = _terse + def isDiffy = _diff + + def setVerbose() { + _verbose = true + } + def setDebug() { + _debug = true + } + def setTerse() { + _terse = true + } + def setDiffOnFail() { + _diff = true + } def verbose(msg: String) { - if (_verbose) { - outline("debug: ") - println(msg) - } + if (isVerbose) + System.err.println(msg) } def debug(msg: String) { - if (isPartestDebug) { - outline("debug: ") - println(msg) - } + if (isDebug) + System.err.println(msg) } } diff --git a/src/partest/scala/tools/partest/nest/PathSettings.scala b/src/partest/scala/tools/partest/nest/PathSettings.scala index 0ba34777a0..030c515947 100644 --- a/src/partest/scala/tools/partest/nest/PathSettings.scala +++ b/src/partest/scala/tools/partest/nest/PathSettings.scala @@ -5,11 +5,9 @@ package scala.tools.partest package nest -import scala.tools.nsc.Properties.{ setProp, propOrEmpty, propOrNone, propOrElse } import scala.tools.nsc.util.ClassPath -import scala.tools.nsc.io -import io.{ Path, File, Directory } -import RunnerUtils._ +import scala.tools.nsc.io.{ Path, File, Directory } +import Path._ object PathSettings { import PartestDefaults.{ testRootDir, srcDirName } @@ -19,6 +17,8 @@ object PathSettings { private def findJar(d: Directory, name: String): Option[File] = findJar(d.files, name) private def findJar(files: Iterator[File], name: String): Option[File] = files filter (_ hasExtension "jar") find { _.name startsWith name } + private def findJarOrFail(name: String, ds: Directory*): File = findJar(ds flatMap (_.files) iterator, name) getOrElse + sys.error(s"'${name}.jar' not found in '${ds map (_.path) mkString ", "}'.") // Directory <root>/test lazy val testRoot: Directory = testRootDir getOrElse { @@ -27,8 +27,8 @@ object PathSettings { candidates find isPartestDir getOrElse sys.error("Directory 'test' not found.") } - // Directory <root>/test/files - lazy val srcDir = Directory(testRoot / srcDirName toCanonical) + // Directory <root>/test/files or .../scaladoc + def srcDir = Directory(testRoot / srcDirName toCanonical) // Directory <root>/test/files/lib lazy val srcLibDir = Directory(srcDir / "lib") @@ -73,8 +73,14 @@ object PathSettings { sys.error("No scalacheck jar found in '%s' or '%s'".format(buildPackLibDir, srcLibDir)) } + lazy val testInterface: File = findJarOrFail("test-interface", buildPackLibDir, srcLibDir) + lazy val diffUtils: File = findJar(buildPackLibDir.files, "diffutils") getOrElse sys.error(s"No diffutils.jar found in '$buildPackLibDir'.") + + /** The platform-specific support jar, `tools.jar`. + */ + lazy val platformTools: Option[File] = PathResolver.SupplementalLocations.platformTools } class PathSettings() { diff --git a/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala b/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala index 700667afcf..734affa153 100644 --- a/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala +++ b/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala @@ -3,8 +3,6 @@ * @author Philipp Haller */ -// $Id$ - package scala.tools.partest package nest @@ -12,7 +10,6 @@ import scala.tools.nsc.Properties.{ setProp, propOrEmpty } import scala.tools.nsc.util.ClassPath import scala.tools.nsc.io import io.Path -import RunnerUtils._ import java.net.URLClassLoader /* This class is used to load an instance of DirectRunner using @@ -28,6 +25,12 @@ class ReflectiveRunner { // was used to start the runner. val sepRunnerClassName = "scala.tools.partest.nest.ConsoleRunner" + private def searchPath(option: String, as: List[String]): Option[String] = as match { + case `option` :: r :: _ => Some(r) + case _ :: rest => searchPath(option, rest) + case Nil => None + } + def main(args: String) { val argList = (args.split("\\s")).toList @@ -47,23 +50,18 @@ class ReflectiveRunner { else // auto detection new ConsoleFileManager - import fileManager. - { latestCompFile, latestReflectFile, latestLibFile, latestPartestFile, latestFjbgFile, latestScalapFile, latestActorsFile } - val files = - Array(latestCompFile, latestReflectFile, latestLibFile, latestPartestFile, latestFjbgFile, latestScalapFile, latestActorsFile) map (x => io.File(x)) - - val sepUrls = files map (_.toURL) - // this seems to be the core classloader that determines which classes can be found when running partest from the test/partest script - var sepLoader = new URLClassLoader(sepUrls, null) - // this is a workaround for https://issues.scala-lang.org/browse/SI-5433 + // when that bug is fixed, the addition of PathSettings.srcCodeLib can be removed // we hack into the classloader that will become parent classloader for scalac // this way we ensure that reflective macro lookup will pick correct Code.lift - // it's also used to inject diffutils into the classpath when running partest from the test/partest script - sepLoader = new URLClassLoader((PathSettings.srcCodeLib +: (PathSettings.diffUtils +: files)) map (_.toURL), null) + // it's also used to inject diffutils into the classpath when running partest from the test/partest script + val srcCodeLibAndDiff = List(PathSettings.srcCodeLib, PathSettings.diffUtils, PathSettings.testInterface) + val sepUrls = srcCodeLibAndDiff.map(_.toURI.toURL) ::: fileManager.latestUrls + // this seems to be the core classloader that determines which classes can be found when running partest from the test/partest script + val sepLoader = new URLClassLoader(sepUrls.toArray, null) if (isPartestDebug) - println("Loading classes from:\n" + sepUrls.mkString("\n")) + println("Loading classes from:\n " + fileManager.latestUrls.mkString("\n ")) // @partest maintainer: it seems to me that commented lines are incorrect // if classPath is not empty, then it has been provided by the --classpath option @@ -74,11 +72,11 @@ class ReflectiveRunner { // case Some(cp) => Nil // case _ => files.toList map (_.path) //} - val paths = files.toList map (_.path) - val newClasspath = ClassPath.join(paths: _*) + setProp("java.class.path", ClassPath.join(fileManager.latestPaths: _*)) - setProp("java.class.path", newClasspath) + // don't let partest find pluginsdir; in ant build, standard plugin has dedicated test suite + //setProp("scala.home", latestLibFile.parent.parent.path) setProp("scala.home", "") if (isPartestDebug) diff --git a/src/partest/scala/tools/partest/nest/Runner.scala b/src/partest/scala/tools/partest/nest/Runner.scala new file mode 100644 index 0000000000..a53698eb77 --- /dev/null +++ b/src/partest/scala/tools/partest/nest/Runner.scala @@ -0,0 +1,901 @@ +/* NEST (New Scala Test) + * Copyright 2007-2013 LAMP/EPFL + * @author Paul Phillips + */ +package scala.tools.partest +package nest + +import java.io.{ Console => _, _ } +import java.net.URL +import java.nio.charset.{ Charset, CharsetDecoder, CharsetEncoder, CharacterCodingException, CodingErrorAction => Action } +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit.NANOSECONDS +import scala.collection.mutable.ListBuffer +import scala.concurrent.duration.Duration +import scala.io.Codec +import scala.reflect.internal.FatalError +import scala.sys.process.{ Process, ProcessLogger } +import scala.tools.nsc.Properties.{ envOrElse, isWin, jdkHome, javaHome, propOrElse, propOrEmpty, setProp } +import scala.tools.nsc.{ Settings, CompilerCommand, Global } +import scala.tools.nsc.io.{ AbstractFile, PlainFile } +import scala.tools.nsc.reporters.ConsoleReporter +import scala.tools.nsc.util.{ Exceptional, ScalaClassLoader, stackTraceString } +import scala.tools.scalap.Main.decompileScala +import scala.tools.scalap.scalax.rules.scalasig.ByteCode +import scala.util.{ Try, Success, Failure } +import ClassPath.{ join, split } +import PartestDefaults.{ javaCmd, javacCmd } +import TestState.{ Pass, Fail, Crash, Uninitialized, Updated } + +trait PartestRunSettings { + def gitPath: Path + def reportPath: Path + def logPath: Path + + def testPaths: List[Path] + + def gitDiffOptions: List[String] + def extraScalacOptions: List[String] + def extraJavaOptions: List[String] +} + +class TestTranscript { + import NestUI.color._ + private val buf = ListBuffer[String]() + private def pass(s: String) = bold(green("% ")) + s + private def fail(s: String) = bold(red("% ")) + s + + def add(action: String): this.type = { buf += action ; this } + def append(text: String) { val s = buf.last ; buf.trimEnd(1) ; buf += (s + text) } + + // Colorize prompts according to pass/fail + def fail: List[String] = buf.toList match { + case Nil => Nil + case xs => (xs.init map pass) :+ fail(xs.last) + } +} + +/** Run a single test. Rubber meets road. */ +class Runner(val testFile: File, fileManager: FileManager, val testRunParams: TestRunParams) { + import fileManager._ + + // Override to true to have the outcome of this test displayed + // whether it passes or not; in general only failures are reported, + // except for a . per passing test to show progress. + def isEnumeratedTest = false + + private var _lastState: TestState = null + private var _transcript = new TestTranscript + + def lastState = if (_lastState == null) Uninitialized(testFile) else _lastState + def setLastState(s: TestState) = _lastState = s + def transcript: List[String] = _transcript.fail ++ logFile.fileLines + def pushTranscript(msg: String) = _transcript add msg + + val parentFile = testFile.getParentFile + val kind = parentFile.getName + val fileBase = basename(testFile.getName) + val logFile = new File(parentFile, s"$fileBase-$kind.log") + val outFile = logFile changeExtension "obj" + val checkFile = testFile changeExtension "check" + val flagsFile = testFile changeExtension "flags" + val testIdent = testFile.testIdent // e.g. pos/t1234 + + lazy val outDir = { outFile.mkdirs() ; outFile } + + type RanOneTest = (Boolean, LogContext) + + def showCrashInfo(t: Throwable) { + System.err.println("Crashed running test $testIdent: " + t) + if (!isPartestTerse) + System.err.println(stackTraceString(t)) + } + protected def crashHandler: PartialFunction[Throwable, TestState] = { + case t: InterruptedException => + genTimeout() + case t: Throwable => + showCrashInfo(t) + logFile.appendAll(stackTraceString(t)) + genCrash(t) + } + + def genPass() = Pass(testFile) + def genFail(reason: String) = Fail(testFile, reason, _transcript.fail) + def genTimeout() = Fail(testFile, "timed out", _transcript.fail) + def genCrash(caught: Throwable) = Crash(testFile, caught, _transcript.fail) + def genUpdated() = Updated(testFile) + + def speclib = PathSettings.srcSpecLib.toString // specialization lib + def codelib = PathSettings.srcCodeLib.toString // reify lib + + // Prepend to a classpath, but without incurring duplicate entries + def prependTo(classpath: String, path: String): String = { + val segments = ClassPath split classpath + + if (segments startsWith path) classpath + else ClassPath.join(path :: segments distinct: _*) + } + + def prependToJavaClasspath(path: String) { + val jcp = sys.props.getOrElse("java.class.path", "") + prependTo(jcp, path) match { + case `jcp` => + case cp => sys.props("java.class.path") = cp + } + } + def prependToClasspaths(s: Settings, path: String) { + prependToJavaClasspath(path) + val scp = s.classpath.value + prependTo(scp, path) match { + case `scp` => + case cp => s.classpath.value = cp + } + } + + private def workerError(msg: String): Unit = System.err.println("Error: " + msg) + + def javac(files: List[File]): TestState = { + // compile using command-line javac compiler + val args = Seq( + javacCmd, + "-d", + outDir.getAbsolutePath, + "-classpath", + join(outDir.toString, CLASSPATH) + ) ++ files.map(_.getAbsolutePath) + + pushTranscript(args mkString " ") + val captured = StreamCapture(runCommand(args, logFile)) + if (captured.result) genPass() else { + logFile appendAll captured.stderr + genFail("java compilation failed") + } + } + + def testPrompt = kind match { + case "res" => "nsc> " + case _ => "% " + } + + /** Evaluate an action body and update the test state. + * @param failFn optionally map a result to a test state. + */ + def nextTestAction[T](body: => T)(failFn: PartialFunction[T, TestState]): T = { + val result = body + setLastState( if (failFn isDefinedAt result) failFn(result) else genPass() ) + result + } + def nextTestActionExpectTrue(reason: String, body: => Boolean): Boolean = ( + nextTestAction(body) { case false => genFail(reason) } + ) + def nextTestActionFailing(reason: String): Boolean = nextTestActionExpectTrue(reason, false) + + private def assembleTestCommand(outDir: File, logFile: File): List[String] = { + // check whether there is a ".javaopts" file + val argsFile = testFile changeExtension "javaopts" + val argString = file2String(argsFile) + if (argString != "") + NestUI.verbose("Found javaopts file '%s', using options: '%s'".format(argsFile, argString)) + + val testFullPath = testFile.getAbsolutePath + + // Note! As this currently functions, JAVA_OPTS must precede argString + // because when an option is repeated to java only the last one wins. + // That means until now all the .javaopts files were being ignored because + // they all attempt to change options which are also defined in + // partest.java_opts, leading to debug output like: + // + // debug: Found javaopts file 'files/shootout/message.scala-2.javaopts', using options: '-Xss32k' + // debug: java -Xss32k -Xss2m -Xms256M -Xmx1024M -classpath [...] + val extras = if (isPartestDebug) List("-Dpartest.debug=true") else Nil + val propertyOptions = List( + "-Dfile.encoding=UTF-8", + "-Djava.library.path="+logFile.getParentFile.getAbsolutePath, + "-Dpartest.output="+outDir.getAbsolutePath, + "-Dpartest.lib="+LATEST_LIB, + "-Dpartest.reflect="+LATEST_REFLECT, + "-Dpartest.cwd="+outDir.getParent, + "-Dpartest.test-path="+testFullPath, + "-Dpartest.testname="+fileBase, + "-Djavacmd="+javaCmd, + "-Djavaccmd="+javacCmd, + "-Duser.language=en", + "-Duser.country=US" + ) ++ extras + + val classpath = if (extraClasspath != "") join(extraClasspath, CLASSPATH) else CLASSPATH + + javaCmd +: ( + (JAVA_OPTS.split(' ') ++ extraJavaOptions.split(' ') ++ argString.split(' ')).map(_.trim).filter(_ != "").toList ++ Seq( + "-classpath", + join(outDir.toString, classpath) + ) ++ propertyOptions ++ Seq( + "scala.tools.nsc.MainGenericRunner", + "-usejavacp", + "Test", + "jvm" + ) + ) + } + + /** Runs command redirecting standard out and + * error out to output file. + */ + private def runCommand(args: Seq[String], outFile: File): Boolean = { + //(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 + } + + private def execTest(outDir: File, logFile: File): Boolean = { + val cmd = assembleTestCommand(outDir, logFile) + + pushTranscript((cmd mkString s" \\$EOL ") + " > " + logFile.getName) + nextTestAction(runCommand(cmd, logFile)) { + case false => + _transcript append EOL + logFile.fileContents + genFail("non-zero exit code") + } + } + + override def toString = s"""Test($testIdent, lastState = $lastState)""" + + // result is unused + def newTestWriters() = { + val swr = new StringWriter + val wr = new PrintWriter(swr, true) + // diff = "" + + ((swr, wr)) + } + + def fail(what: Any) = { + NestUI.verbose("scalac: compilation of "+what+" failed\n") + false + } + + /** Filter the diff for conditional blocks. + * The check file can contain lines of the form: + * `#partest java7` + * where the line contains a conventional flag name. + * In the diff output, these lines have the form: + * `> #partest java7` + * Blocks which don't apply are filtered out, + * and what remains is the desired diff. + * Line edit commands such as `0a1,6` don't count + * as diff, so return a nonempty diff only if + * material diff output was seen. + * Filtering the diff output (instead of every check + * file) means that we only post-process a test that + * might be failing, in the normal case. + */ + def diffilter(d: String) = { + import scala.util.Properties.{javaVersion, isAvian} + val prefix = "#partest" + val margin = "> " + val leader = margin + prefix + // use lines in block so labeled? Default to sorry, Charlie. + def retainOn(f: String) = { + val (invert, token) = + if (f startsWith "!") (true, f drop 1) else (false, f) + val cond = token match { + case "java7" => javaVersion startsWith "1.7" + case "java6" => javaVersion startsWith "1.6" + case "avian" => isAvian + case "true" => true + case _ => false + } + if (invert) !cond else cond + } + if (d contains prefix) { + val sb = new StringBuilder + var retain = true // use the current line + var material = false // saw a line of diff + for (line <- d.lines) + if (line startsWith leader) { + val rest = (line stripPrefix leader).trim + retain = retainOn(rest) + } else if (retain) { + if (line startsWith margin) material = true + sb ++= line + sb ++= EOL + } + if (material) sb.toString else "" + } else d + } + + def currentDiff = ( + if (checkFile.canRead) diffilter(compareFiles(logFile, checkFile)) + else compareContents(augmentString(file2String(logFile)).lines.toList, Nil) + ) + + val gitRunner = List("/usr/local/bin/git", "/usr/bin/git") map (f => new java.io.File(f)) find (_.canRead) + val gitDiffOptions = "--ignore-space-at-eol --no-index " + propOrEmpty("partest.git_diff_options") + // --color=always --word-diff + + def gitDiff(f1: File, f2: File): Option[String] = { + try gitRunner map { git => + val cmd = s"$git diff $gitDiffOptions $f1 $f2" + val diff = Process(cmd).lines_!.drop(4).map(_ + "\n").mkString + + "\n" + diff + } + catch { case t: Exception => None } + } + + /** Normalize the log output by applying test-specific filters + * and fixing filesystem-specific paths. + * + * Line filters are picked up from `filter: pattern` at the top of sources. + * The filtered line is detected with a simple "contains" test, + * and yes, "filter" means "filter out" in this context. + * + * File paths are detected using the absolute path of the test root. + * A string that looks like a file path is normalized by replacing + * the leading segments (the root) with "$ROOT" and by replacing + * any Windows backslashes with the one true file separator char. + */ + def normalizeLog() { + // Apply judiciously; there are line comments in the "stub implementations" error output. + val slashes = """[/\\]+""".r + def squashSlashes(s: String) = slashes replaceAllIn (s, "/") + + // this string identifies a path and is also snipped from log output. + // to preserve more of the path, could use fileManager.testRootPath + val elided = parentFile.getAbsolutePath + + // something to mark the elision in the log file (disabled) + val ellipsis = "" //".../" // using * looks like a comment + + // no spaces in test file paths below root, because otherwise how to detect end of path string? + val pathFinder = raw"""(?i)\Q${elided}${File.separator}\E([\${File.separator}\w]*)""".r + def canonicalize(s: String): String = ( + pathFinder replaceAllIn (s, m => ellipsis + squashSlashes(m group 1)) + ) + + def masters = { + val files = List(new File(parentFile, "filters"), new File(PathSettings.srcDir.path, "filters")) + files filter (_.exists) flatMap (_.fileLines) map (_.trim) filter (s => !(s startsWith "#")) + } + val filters = toolArgs("filter", split = false) ++ masters + val elisions = ListBuffer[String]() + //def lineFilter(s: String): Boolean = !(filters exists (s contains _)) + def lineFilter(s: String): Boolean = ( + filters map (_.r) forall { r => + val res = (r findFirstIn s).isEmpty + if (!res) elisions += s + res + } + ) + + logFile.mapInPlace(canonicalize)(lineFilter) + if (isPartestVerbose && elisions.nonEmpty) { + import NestUI.color._ + val emdash = bold(yellow("--")) + pushTranscript(s"filtering ${logFile.getName}$EOL${elisions mkString (emdash, EOL + emdash, EOL)}") + } + } + + 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(fileManager.updateCheck) + ) + pushTranscript(s"diff $logFile $checkFile") + nextTestAction(updating) { + case Some(true) => + NestUI.verbose("Updating checkfile " + checkFile) + checkFile 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 + } + _transcript append bestDiff + genFail("output differs") + // TestState.fail("output differs", "output differs", + // genFail("output differs") + // TestState.Fail("output differs", bestDiff) + case None => genPass() // redundant default case + } getOrElse true + } + + /** 1. Creates log file and output directory. + * 2. Runs script function, providing log file and output directory as arguments. + * 2b. or, just run the script without context and return a new context + */ + def runInContext(body: => Boolean): (Boolean, LogContext) = { + val (swr, wr) = newTestWriters() + val succeeded = body + (succeeded, LogContext(logFile, swr, wr)) + } + + /** Grouped files in group order, and lex order within each group. */ + def groupedFiles(sources: List[File]): List[List[File]] = ( + if (sources.tail.nonEmpty) { + val grouped = sources groupBy (_.group) + grouped.keys.toList.sorted map (k => grouped(k) sortBy (_.getName)) + } + else List(sources) + ) + + /** Source files for the given test file. */ + def sources(file: File): List[File] = ( + if (file.isDirectory) + file.listFiles.toList filter (_.isJavaOrScala) + else + List(file) + ) + + def newCompiler = new DirectCompiler(fileManager) + + def attemptCompile(sources: List[File]): TestState = { + val state = newCompiler.compile(this, flagsForCompilation(sources), sources) + if (!state.isOk) + _transcript append ("\n" + file2String(logFile)) + + state + } + + // snort or scarf all the contributing flags files + def flagsForCompilation(sources: List[File]): List[String] = { + def argsplitter(s: String) = words(s) filter (_.nonEmpty) + val perTest = argsplitter(flagsFile.fileContents) + val perGroup = if (testFile.isDirectory) { + sources flatMap { f => SFile(Path(f) changeExtension "flags").safeSlurp map argsplitter getOrElse Nil } + } else Nil + perTest ++ perGroup + } + + def toolArgs(tool: String, split: Boolean = true): List[String] = { + def argsplitter(s: String) = if (split) words(s) filter (_.nonEmpty) else List(s) + def argsFor(f: File): List[String] = { + import scala.util.matching.Regex + val p = new Regex(s"(?:.*\\s)?${tool}:(?:\\s*)(.*)?", "args") + val max = 10 + val src = Path(f).toFile.chars(codec) + val args = try { + src.getLines take max collectFirst { + case s if (p findFirstIn s).nonEmpty => for (m <- p findFirstMatchIn s) yield m group "args" + } + } finally src.close() + args.flatten map argsplitter getOrElse Nil + } + sources(testFile) flatMap argsFor + } + + abstract class CompileRound { + def fs: List[File] + def result: TestState + def description: String + + def fsString = fs map (_.toString stripPrefix parentFile.toString + "/") mkString " " + def isOk = result.isOk + def mkScalacString(): String = { + val flags = file2String(flagsFile) match { + case "" => "" + case s => " " + s + } + s"""scalac $fsString""" + } + override def toString = description + ( if (result.isOk) "" else "\n" + result.status ) + } + case class OnlyJava(fs: List[File]) extends CompileRound { + def description = s"""javac $fsString""" + lazy val result = { pushTranscript(description) ; javac(fs) } + } + case class OnlyScala(fs: List[File]) extends CompileRound { + def description = mkScalacString() + lazy val result = { pushTranscript(description) ; attemptCompile(fs) } + } + case class ScalaAndJava(fs: List[File]) extends CompileRound { + def description = mkScalacString() + lazy val result = { pushTranscript(description) ; attemptCompile(fs) } + } + + def compilationRounds(file: File): List[CompileRound] = ( + (groupedFiles(sources(file)) map mixedCompileGroup).flatten + ) + def mixedCompileGroup(allFiles: List[File]): List[CompileRound] = { + val (scalaFiles, javaFiles) = allFiles partition (_.isScala) + val isMixed = javaFiles.nonEmpty && scalaFiles.nonEmpty + val round1 = if (scalaFiles.isEmpty) None else Some(ScalaAndJava(allFiles)) + val round2 = if (javaFiles.isEmpty) None else Some(OnlyJava(javaFiles)) + val round3 = if (!isMixed) None else Some(OnlyScala(scalaFiles)) + + List(round1, round2, round3).flatten + } + + def runNegTest() = runInContext { + val rounds = compilationRounds(testFile) + + // failing means Does Not Compile + val failing = rounds find (x => nextTestActionExpectTrue("compilation failed", x.isOk) == false) + + // which means passing if it checks and didn't crash the compiler + // or, OK, we'll let you crash the compiler with a FatalError if you supply a check file + def checked(r: CompileRound) = r.result match { + case Crash(_, t, _) if !checkFile.canRead || !t.isInstanceOf[FatalError] => false + case _ => diffIsOk + } + + failing map (checked) getOrElse nextTestActionFailing("expected compilation failure") + } + + def runTestCommon(andAlso: => Boolean): (Boolean, LogContext) = runInContext { + compilationRounds(testFile).forall(x => nextTestActionExpectTrue("compilation failed", x.isOk)) && andAlso + } + + // Apache Ant 1.6 or newer + def ant(args: Seq[String], output: File): Boolean = { + val antDir = Directory(envOrElse("ANT_HOME", "/opt/ant/")) + val antLibDir = Directory(antDir / "lib") + val antLauncherPath = SFile(antLibDir / "ant-launcher.jar").path + val antOptions = + if (NestUI._verbose) List("-verbose", "-noinput") + else List("-noinput") + val cmd = javaCmd +: ( + JAVA_OPTS.split(' ').map(_.trim).filter(_ != "") ++ Seq( + "-classpath", + antLauncherPath, + "org.apache.tools.ant.launch.Launcher" + ) ++ antOptions ++ args + ) + + runCommand(cmd, output) + } + + def runAntTest(): (Boolean, LogContext) = { + val (swr, wr) = newTestWriters() + + val succeeded = try { + val binary = "-Dbinary="+( + if (fileManager.LATEST_LIB endsWith "build/quick/classes/library") "quick" + else if (fileManager.LATEST_LIB endsWith "build/pack/lib/scala-library.jar") "pack" + else if (fileManager.LATEST_LIB endsWith "dists/latest/lib/scala-library.jar/") "latest" + else "installed" + ) + val args = Array(binary, "-logfile", logFile.getPath, "-file", testFile.getPath) + NestUI.verbose("ant "+args.mkString(" ")) + + pushTranscript(s"ant ${args.mkString(" ")}") + nextTestActionExpectTrue("ant failed", ant(args, logFile)) && diffIsOk + } + catch { // *catch-all* + case e: Exception => + NestUI.warning("caught "+e) + false + } + + (succeeded, LogContext(logFile, swr, wr)) + } + + def extraClasspath = kind match { + case "specialized" => PathSettings.srcSpecLib.toString + case _ => "" + } + def extraJavaOptions = kind match { + case "instrumented" => "-javaagent:"+PathSettings.instrumentationAgentLib + case _ => "" + } + + def runScalacheckTest() = runTestCommon { + NestUI verbose f"compilation of $testFile succeeded%n" + + // this classloader is test specific: its parent contains library classes and others + val loader = { + import PathSettings.scalaCheck + val locations = List(outDir, scalaCheck.jfile) map (_.getAbsoluteFile.toURI.toURL) + ScalaClassLoader.fromURLs(locations, getClass.getClassLoader) + } + val logWriter = new PrintStream(new FileOutputStream(logFile), true) + + def runInFramework(): Boolean = { + import org.scalatools.testing._ + val f: Framework = loader.instantiate[Framework]("org.scalacheck.ScalaCheckFramework") + val logger = new Logger { + def ansiCodesSupported = false //params.env.isSet("colors") + def error(msg: String) = logWriter println msg + def warn(msg: String) = logWriter println msg + def info(msg: String) = logWriter println msg + def debug(msg: String) = logWriter println msg + def trace(t: Throwable) = t printStackTrace logWriter + } + var bad = 0 + val handler = new EventHandler { + // testName, description, result, error + // Result = Success, Failure, Error, Skipped + def handle(event: Event): Unit = event.result match { + case Result.Success => + //case Result.Skipped => // an exhausted test is skipped, therefore bad + case _ => bad += 1 + } + } + val loggers = Array(logger) + val r = f.testRunner(loader, loggers).asInstanceOf[Runner2] // why? + val claas = "Test" + val fingerprint = f.tests collectFirst { case x: SubclassFingerprint if x.isModule => x } + val args = toolArgs("scalacheck") + vlog(s"Run $testFile with args $args") + // set the context class loader for scaladoc/scalacheck tests (FIX ME) + ScalaClassLoader(testRunParams.scalaCheckParentClassLoader).asContext { + r.run(claas, fingerprint.get, handler, args.toArray) // synchronous? + } + val ok = (bad == 0) + if (!ok) _transcript append logFile.fileContents + ok + } + try nextTestActionExpectTrue("ScalaCheck test failed", runInFramework()) finally logWriter.close() + } + + def runResidentTest() = { + // simulate resident compiler loop + val prompt = "\nnsc> " + val (swr, wr) = newTestWriters() + + NestUI.verbose(this+" running test "+fileBase) + val dir = parentFile + val resFile = new File(dir, fileBase + ".res") + + // run compiler in resident mode + // $SCALAC -d "$os_dstbase".obj -Xresident -sourcepath . "$@" + val sourcedir = logFile.getParentFile.getAbsoluteFile + val sourcepath = sourcedir.getAbsolutePath+File.separator + NestUI.verbose("sourcepath: "+sourcepath) + + val argList = List( + "-d", outDir.getAbsoluteFile.getPath, + "-Xresident", + "-sourcepath", sourcepath) + + // configure input/output files + val logOut = new FileOutputStream(logFile) + val logWriter = new PrintStream(logOut, true) + val resReader = new BufferedReader(new FileReader(resFile)) + val logConsoleWriter = new PrintWriter(new OutputStreamWriter(logOut), true) + + // create compiler + val settings = new Settings(workerError) + settings.sourcepath.value = sourcepath + settings.classpath.value = fileManager.CLASSPATH + val reporter = new ConsoleReporter(settings, scala.Console.in, logConsoleWriter) + val command = new CompilerCommand(argList, settings) + object compiler extends Global(command.settings, reporter) + + def resCompile(line: String): Boolean = { + // NestUI.verbose("compiling "+line) + val cmdArgs = (line split ' ').toList map (fs => new File(dir, fs).getAbsolutePath) + // NestUI.verbose("cmdArgs: "+cmdArgs) + val sett = new Settings(workerError) + sett.sourcepath.value = sourcepath + val command = new CompilerCommand(cmdArgs, sett) + // "scalac " + command.files.mkString(" ") + pushTranscript("scalac " + command.files.mkString(" ")) + nextTestActionExpectTrue( + "compilation failed", + command.ok && { + (new compiler.Run) compile command.files + !reporter.hasErrors + } + ) + } + def loop(): Boolean = { + logWriter.print(prompt) + resReader.readLine() match { + case null | "" => logWriter.close() ; true + case line => resCompile(line) && loop() + } + } + // res/t687.res depends on ignoring its compilation failure + // and just looking at the diff, so I made them all do that + // because this is long enough. + if (!Output.withRedirected(logWriter)(try loop() finally resReader.close())) + setLastState(genPass()) + + (diffIsOk, LogContext(logFile, swr, wr)) + } + + def run(): TestState = { + if (kind == "neg" || (kind endsWith "-neg")) runNegTest() + else kind match { + case "pos" => runTestCommon(true) + case "ant" => runAntTest() + case "scalacheck" => runScalacheckTest() + case "res" => runResidentTest() + case "scalap" => runScalapTest() + case "script" => runScriptTest() + case _ => runTestCommon(execTest(outDir, logFile) && diffIsOk) + } + + lastState + } + + def runScalapTest() = runTestCommon { + val isPackageObject = testFile.getName startsWith "package" + val className = testFile.getName.stripSuffix(".scala").capitalize + (if (!isPackageObject) "" else ".package") + val loader = ScalaClassLoader.fromURLs(List(outDir.toURI.toURL), this.getClass.getClassLoader) + val byteCode = ByteCode forClass (loader loadClass className) + val result = decompileScala(byteCode.bytes, isPackageObject) + + logFile writeAll result + diffIsOk + } + def runScriptTest() = { + import scala.sys.process._ + val (swr, wr) = newTestWriters() + + val args = file2String(testFile changeExtension "args") + val cmdFile = if (isWin) testFile changeExtension "bat" else testFile + val succeeded = (((cmdFile + " " + args) #> logFile !) == 0) && diffIsOk + + (succeeded, LogContext(logFile, swr, wr)) + } + + def cleanup() { + if (lastState.isOk) + logFile.delete() + if (!isPartestDebug) + Directory(outDir).deleteRecursively() + } +} + +case class TestRunParams(val scalaCheckParentClassLoader: ScalaClassLoader) + +/** Extended by Ant- and ConsoleRunner for running a set of tests. */ +trait DirectRunner { + def fileManager: FileManager + + import PartestDefaults.{ numThreads, waitTime } + + Thread.setDefaultUncaughtExceptionHandler( + new Thread.UncaughtExceptionHandler { + def uncaughtException(thread: Thread, t: Throwable) { + val t1 = Exceptional unwrap t + System.err.println(s"Uncaught exception on thread $thread: $t1") + t1.printStackTrace() + } + } + ) + def runTestsForFiles(kindFiles: List[File], kind: String): List[TestState] = { + + NestUI.resetTestNumber(kindFiles.size) + + // this special class loader is for the benefit of scaladoc tests, which need a class path + import PathSettings.{ testInterface, scalaCheck } + val allUrls = scalaCheck.toURL :: testInterface.toURL :: fileManager.latestUrls + val parentClassLoader = ScalaClassLoader fromURLs allUrls + // add scalacheck.jar to a special classloader, but use our loader as parent with test-interface + //val parentClassLoader = ScalaClassLoader fromURLs (List(scalaCheck.toURL), getClass().getClassLoader) + val pool = Executors newFixedThreadPool numThreads + val manager = new RunnerManager(kind, fileManager, TestRunParams(parentClassLoader)) + val futures = kindFiles map (f => pool submit callable(manager runTest f)) + + pool.shutdown() + Try (pool.awaitTermination(waitTime) { + throw TimeoutException(waitTime) + }) match { + case Success(_) => futures map (_.get) + case Failure(e) => + e match { + case TimeoutException(d) => + NestUI warning "Thread pool timeout elapsed before all tests were complete!" + case ie: InterruptedException => + NestUI warning "Thread pool was interrupted" + ie.printStackTrace() + } + pool.shutdownNow() // little point in continuing + // try to get as many completions as possible, in case someone cares + val results = for (f <- futures) yield { + try { + Some(f.get(0, NANOSECONDS)) + } catch { + case _: Throwable => None + } + } + results.flatten + } + } +} + +case class TimeoutException(duration: Duration) extends RuntimeException + +class LogContext(val file: File, val writers: Option[(StringWriter, PrintWriter)]) + +object LogContext { + def apply(file: File, swr: StringWriter, wr: PrintWriter): LogContext = { + require (file != null) + new LogContext(file, Some((swr, wr))) + } + def apply(file: File): LogContext = new LogContext(file, None) +} + +object Output { + object outRedirect extends Redirecter(out) + object errRedirect extends Redirecter(err) + + System.setOut(outRedirect) + System.setErr(errRedirect) + + import scala.util.DynamicVariable + private def out = java.lang.System.out + private def err = java.lang.System.err + private val redirVar = new DynamicVariable[Option[PrintStream]](None) + + class Redirecter(stream: PrintStream) extends PrintStream(new OutputStream { + def write(b: Int) = withStream(_ write b) + + private def withStream(f: PrintStream => Unit) = f(redirVar.value getOrElse stream) + + override def write(b: Array[Byte]) = withStream(_ write b) + override def write(b: Array[Byte], off: Int, len: Int) = withStream(_.write(b, off, len)) + override def flush = withStream(_.flush) + override def close = withStream(_.close) + }) + + // this supports thread-safe nested output redirects + def withRedirected[T](newstream: PrintStream)(func: => T): T = { + // note down old redirect destination + // this may be None in which case outRedirect and errRedirect print to stdout and stderr + val saved = redirVar.value + // set new redirecter + // this one will redirect both out and err to newstream + redirVar.value = Some(newstream) + + try func + finally { + newstream.flush() + redirVar.value = saved + } + } +} + +/** Use a Runner to run a test. */ +class RunnerManager(kind: String, fileManager: FileManager, params: TestRunParams) { + import fileManager._ + fileManager.CLASSPATH += File.pathSeparator + PathSettings.scalaCheck + fileManager.CLASSPATH += File.pathSeparator + PathSettings.diffUtils // needed to put diffutils on test/partest's classpath + + def runTest(testFile: File): TestState = { + val runner = new Runner(testFile, fileManager, params) + + // when option "--failed" is provided execute test only if log + // is present (which means it failed before) + if (fileManager.failed && !runner.logFile.canRead) + runner.genPass() + else { + val (state, elapsed) = + try timed(runner.run()) + catch { + case t: Throwable => throw new RuntimeException(s"Error running $testFile", t) + } + NestUI.reportTest(state) + runner.cleanup() + state + } + } +} diff --git a/src/partest/scala/tools/partest/nest/RunnerManager.scala b/src/partest/scala/tools/partest/nest/RunnerManager.scala deleted file mode 100644 index f80f6f38fd..0000000000 --- a/src/partest/scala/tools/partest/nest/RunnerManager.scala +++ /dev/null @@ -1,862 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2013 LAMP/EPFL - * @author Philipp Haller - */ - -package scala.tools.partest -package nest - -import java.io._ -import java.net.URL -import java.util.{ Timer, TimerTask } - -import scala.tools.nsc.Properties.{ jdkHome, javaHome, propOrElse } -import scala.util.Properties.{ envOrElse, isWin } -import scala.tools.nsc.{ Settings, CompilerCommand, Global } -import scala.tools.nsc.io.{ AbstractFile, PlainFile, Path, Directory, File => SFile } -import scala.tools.nsc.reporters.ConsoleReporter -import scala.tools.nsc.util.{ ClassPath, FakePos, ScalaClassLoader, stackTraceString } -import ClassPath.{ join, split } -import scala.tools.scalap.scalax.rules.scalasig.ByteCode -import scala.collection.{ mutable, immutable } -import scala.tools.nsc.interactive.{ BuildManager, RefinedBuildManager } -import scala.sys.process._ -import java.util.concurrent.{ Executors, TimeUnit, TimeoutException } -import PartestDefaults.{ javaCmd, javacCmd } - -class LogContext(val file: File, val writers: Option[(StringWriter, PrintWriter)]) - -object LogContext { - def apply(file: File, swr: StringWriter, wr: PrintWriter): LogContext = { - require (file != null) - new LogContext(file, Some((swr, wr))) - } - def apply(file: File): LogContext = new LogContext(file, None) -} - -object Output { - object outRedirect extends Redirecter(out) - object errRedirect extends Redirecter(err) - - System.setOut(outRedirect) - System.setErr(errRedirect) - - import scala.util.DynamicVariable - private def out = java.lang.System.out - private def err = java.lang.System.err - private val redirVar = new DynamicVariable[Option[PrintStream]](None) - - class Redirecter(stream: PrintStream) extends PrintStream(new OutputStream { - def write(b: Int) = withStream(_ write b) - - private def withStream(f: PrintStream => Unit) = f(redirVar.value getOrElse stream) - - override def write(b: Array[Byte]) = withStream(_ write b) - override def write(b: Array[Byte], off: Int, len: Int) = withStream(_.write(b, off, len)) - override def flush = withStream(_.flush) - override def close = withStream(_.close) - }) - - // this supports thread-safe nested output redirects - def withRedirected[T](newstream: PrintStream)(func: => T): T = { - // note down old redirect destination - // this may be None in which case outRedirect and errRedirect print to stdout and stderr - val saved = redirVar.value - // set new redirecter - // this one will redirect both out and err to newstream - redirVar.value = Some(newstream) - - try func - finally { - newstream.flush() - redirVar.value = saved - } - } -} - -class RunnerManager(kind: String, val fileManager: FileManager, params: TestRunParams) { - import fileManager._ - - val compileMgr = new CompileManager(fileManager) - fileManager.CLASSPATH += File.pathSeparator + PathSettings.scalaCheck - fileManager.CLASSPATH += File.pathSeparator + PathSettings.diffUtils // needed to put diffutils on test/partest's classpath - - private def compareFiles(f1: File, f2: File): String = - try fileManager.compareFiles(f1, f2) - catch { case t: Exception => t.toString } - - /** This does something about absolute paths and file separator - * chars before diffing. - */ - private def replaceSlashes(dir: File, s: String): String = { - val base = (dir.getAbsolutePath + File.separator).replace('\\', '/') - var regex = """\Q%s\E""" format base - if (isWin) regex = "(?i)" + regex - s.replace('\\', '/').replaceAll(regex, "") - } - - private def workerError(msg: String): Unit = System.err.println("Error: " + msg) - - private def printInfoStart(file: File, printer: PrintWriter) { - NestUI.outline("testing: ", printer) - val filesdir = file.getAbsoluteFile.getParentFile.getParentFile - val testdir = filesdir.getParentFile - val totalWidth = 56 - val name = { - // 1. try with [...]/files/run/test.scala - val name = file.getAbsolutePath drop testdir.getAbsolutePath.length - if (name.length <= totalWidth) name - // 2. try with [...]/run/test.scala - else file.getAbsolutePath drop filesdir.getAbsolutePath.length - } - NestUI.normal("[...]%s%s".format(name, " " * (totalWidth - name.length)), printer) - } - - private def printInfoEnd(success: Boolean, printer: PrintWriter) { - NestUI.normal("[", printer) - if (success) NestUI.success(" OK ", printer) - else NestUI.failure("FAILED", printer) - NestUI.normal("]\n", printer) - } - - private def printInfoTimeout(printer: PrintWriter) { - NestUI.normal("[", printer) - NestUI.failure("TIMOUT", printer) - NestUI.normal("]\n", printer) - } - - private def javac(outDir: File, files: List[File], output: File): CompilationOutcome = { - // compile using command-line javac compiler - val args = Seq( - javacCmd, - "-d", - outDir.getAbsolutePath, - "-classpath", - join(outDir.toString, CLASSPATH) - ) ++ files.map("" + _) - - try if (runCommand(args, output)) CompileSuccess else CompileFailed - catch exHandler(output, "javac command failed:\n" + args.map(" " + _ + "\n").mkString + "\n", CompilerCrashed) - } - - /** Runs command redirecting standard out and error out to output file. - * Overloaded to accept a sequence of arguments. - */ - private def runCommand(args: Seq[String], outFile: File): Boolean = { - NestUI.verbose("running command:\n"+args.map(" " + _ + "\n").mkString) - runCommandImpl(Process(args), outFile) - } - - /** Runs command redirecting standard out and error out to output file. - * Overloaded to accept a single string = concatenated command + arguments. - */ - private def runCommand(command: String, outFile: File): Boolean = { - NestUI.verbose("running command:"+command) - runCommandImpl(Process(command), outFile) - } - - private def runCommandImpl(process: => ProcessBuilder, outFile: File): Boolean = { - val exitCode = (process #> outFile !) - // normalize line endings - // System.getProperty("line.separator") should be "\n" here - // so reading a file and writing it back should convert all CRLFs to LFs - SFile(outFile).printlnAll(SFile(outFile).lines.toList: _*) - exitCode == 0 - } - - @inline private def isJava(f: File) = SFile(f) hasExtension "java" - @inline private def isScala(f: File) = SFile(f) hasExtension "scala" - @inline private def isJavaOrScala(f: File) = isJava(f) || isScala(f) - - private def outputLogFile(logFile: File) { - val lines = SFile(logFile).lines - if (lines.nonEmpty) { - NestUI.normal("Log file '" + logFile + "': \n") - lines foreach (x => NestUI.normal(x + "\n")) - } - } - private def logStackTrace(logFile: File, t: Throwable, msg: String): Boolean = { - SFile(logFile).writeAll(msg, stackTraceString(t)) - outputLogFile(logFile) // if running the test threw an exception, output log file - false - } - - private def exHandler[T](logFile: File, msg: String, value: T): PartialFunction[Throwable, T] = { - case e: Exception => logStackTrace(logFile, e, msg) ; value - } - - class Runner(testFile: File) { - var testDiff: String = "" - var passed: Option[Boolean] = None - - val fileBase = basename(testFile.getName) - val logFile = fileManager.getLogFile(testFile, kind) - val parent = testFile.getParentFile - val outDir = new File(parent, "%s-%s.obj".format(fileBase, kind)) - def toDelete = if (isPartestDebug) Nil else List( - if (passed exists (x => x)) Some(logFile) else None, - if (outDir.isDirectory) Some(outDir) else None - ).flatten - - private def createOutputDir(): File = { - outDir.mkdirs() - outDir - } - - private def execTest(outDir: File, logFile: File, classpathPrefix: String = "", javaOpts: String = ""): Boolean = { - // check whether there is a ".javaopts" file - val argsFile = new File(logFile.getParentFile, fileBase + ".javaopts") - val argString = file2String(argsFile) - if (argString != "") - NestUI.verbose("Found javaopts file '%s', using options: '%s'".format(argsFile, argString)) - - val testFullPath = { - val d = new File(logFile.getParentFile, fileBase) - if (d.isDirectory) d.getAbsolutePath - else { - val f = new File(logFile.getParentFile, fileBase + ".scala") - if (f.isFile) f.getAbsolutePath - else "" - } - } - - // Note! As this currently functions, JAVA_OPTS must precede argString - // because when an option is repeated to java only the last one wins. - // That means until now all the .javaopts files were being ignored because - // they all attempt to change options which are also defined in - // partest.java_opts, leading to debug output like: - // - // debug: Found javaopts file 'files/shootout/message.scala-2.javaopts', using options: '-Xss32k' - // debug: java -Xss32k -Xss2m -Xms256M -Xmx1024M -classpath [...] - val extras = if (isPartestDebug) List("-Dpartest.debug=true") else Nil - val propertyOptions = List( - "-Dfile.encoding=UTF-8", - "-Djava.library.path="+logFile.getParentFile.getAbsolutePath, - "-Dpartest.output="+outDir.getAbsolutePath, - "-Dpartest.lib="+LATEST_LIB, - "-Dpartest.reflect="+LATEST_REFLECT, - "-Dpartest.comp="+LATEST_COMP, - "-Dpartest.cwd="+outDir.getParent, - "-Dpartest.test-path="+testFullPath, - "-Dpartest.testname="+fileBase, - "-Djavacmd="+javaCmd, - "-Djavaccmd="+javacCmd, - "-Duser.language=en", - "-Duser.country=US" - ) ++ extras - - val classpath = if (classpathPrefix != "") join(classpathPrefix, CLASSPATH) else CLASSPATH - val cmd = javaCmd +: ( - (JAVA_OPTS.split(' ') ++ javaOpts.split(' ') ++ argString.split(' ')).map(_.trim).filter(_ != "") ++ Seq( - "-classpath", - join(outDir.toString, classpath) - ) ++ propertyOptions ++ Seq( - "scala.tools.nsc.MainGenericRunner", - "-usejavacp", - "Test", - "jvm" - ) - ) - - runCommand(cmd, logFile) - } - - private def getCheckFilePath(dir: File, suffix: String = "") = { - def chkFile(s: String) = (Directory(dir) / "%s%s.check".format(fileBase, s)).toFile - - if (chkFile("").isFile || suffix == "") chkFile("") - else chkFile("-" + suffix) - } - private def getCheckFile(dir: File) = Some(getCheckFilePath(dir, kind)) filter (_.canRead) - - private def compareOutput(dir: File, logFile: File): String = { - val checkFile = getCheckFilePath(dir, kind) - val diff = - if (checkFile.canRead) compareFiles(logFile, checkFile.jfile) - else file2String(logFile) - - // if check file exists, compare with log file - if (diff != "" && fileManager.updateCheck) { - NestUI.verbose("Updating checkfile " + checkFile.jfile) - val toWrite = if (checkFile.exists) checkFile else getCheckFilePath(dir, "") - toWrite writeAll file2String(logFile) - "" - } - else diff - } - - def newTestWriters() = { - val swr = new StringWriter - val wr = new PrintWriter(swr, true) - // diff = "" - - ((swr, wr)) - } - - def fail(what: Any) = { - NestUI.verbose("scalac: compilation of "+what+" failed\n") - false - } - def diffCheck(testFile: File, diff: String) = { - testDiff = diff - testDiff == "" - } - - /** 1. Creates log file and output directory. - * 2. Runs script function, providing log file and output directory as arguments. - */ - def runInContext(file: File, script: (File, File) => Boolean): (Boolean, LogContext) = { - val (swr, wr) = newTestWriters() - printInfoStart(file, wr) - - NestUI.verbose(this+" running test "+fileBase) - val outDir = createOutputDir() - NestUI.verbose("output directory: "+outDir) - - // run test-specific code - val succeeded = try { - if (isPartestDebug) { - val (result, millis) = timed(script(logFile, outDir)) - fileManager.recordTestTiming(file.getPath, millis) - result - } - else script(logFile, outDir) - } - catch exHandler(logFile, "", false) - - (succeeded, LogContext(logFile, swr, wr)) - } - - def groupedFiles(dir: File): List[List[File]] = { - val testFiles = dir.listFiles.toList filter isJavaOrScala - - def isInGroup(f: File, num: Int) = SFile(f).stripExtension endsWith ("_" + num) - val groups = (0 to 9).toList map (num => (testFiles filter (f => isInGroup(f, num))).sorted) - val noGroupSuffix = (testFiles filterNot (groups.flatten contains)).sorted - - noGroupSuffix :: groups filterNot (_.isEmpty) - } - - def compileFilesIn(dir: File, logFile: File, outDir: File): CompilationOutcome = { - def compileGroup(g: List[File]): CompilationOutcome = { - val (scalaFiles, javaFiles) = g partition isScala - val allFiles = javaFiles ++ scalaFiles - - List(1, 2, 3).foldLeft(CompileSuccess: CompilationOutcome) { - case (CompileSuccess, 1) if scalaFiles.nonEmpty => compileMgr.attemptCompile(Some(outDir), allFiles, kind, logFile) // java + scala - case (CompileSuccess, 2) if javaFiles.nonEmpty => javac(outDir, javaFiles, logFile) // java - case (CompileSuccess, 3) if scalaFiles.nonEmpty => compileMgr.attemptCompile(Some(outDir), scalaFiles, kind, logFile) // scala - case (outcome, _) => outcome - } - } - groupedFiles(dir).foldLeft(CompileSuccess: CompilationOutcome) { - case (CompileSuccess, files) => compileGroup(files) - case (outcome, _) => outcome - } - } - - def runTestCommon(file: File, expectFailure: Boolean)( - onSuccess: (File, File) => Boolean, - onFail: (File, File) => Unit = (_, _) => ()): (Boolean, LogContext) = - { - runInContext(file, (logFile: File, outDir: File) => { - val outcome = ( - if (file.isDirectory) compileFilesIn(file, logFile, outDir) - else compileMgr.attemptCompile(None, List(file), kind, logFile) - ) - val result = ( - if (expectFailure) outcome.isNegative - else outcome.isPositive - ) - - if (result) onSuccess(logFile, outDir) - else { onFail(logFile, outDir) ; false } - }) - } - - def runJvmTest(file: File): (Boolean, LogContext) = - runTestCommon(file, expectFailure = false)((logFile, outDir) => { - val dir = file.getParentFile - - // adding codelib.jar to the classpath - // codelib provides the possibility to override standard reify - // this shields the massive amount of reification tests from changes in the API - execTest(outDir, logFile, PathSettings.srcCodeLib.toString) && { - // cannot replace paths here since this also inverts slashes - // which affects a bunch of tests - //fileManager.mapFile(logFile, replaceSlashes(dir, _)) - diffCheck(file, compareOutput(dir, logFile)) - } - }) - - // Apache Ant 1.6 or newer - def ant(args: Seq[String], output: File): Boolean = { - val antDir = Directory(envOrElse("ANT_HOME", "/opt/ant/")) - val antLibDir = Directory(antDir / "lib") - val antLauncherPath = SFile(antLibDir / "ant-launcher.jar").path - val antOptions = - if (NestUI._verbose) List("-verbose", "-noinput") - else List("-noinput") - val cmd = javaCmd +: ( - JAVA_OPTS.split(' ').map(_.trim).filter(_ != "") ++ Seq( - "-classpath", - antLauncherPath, - "org.apache.tools.ant.launch.Launcher" - ) ++ antOptions ++ args - ) - - try runCommand(cmd, output) - catch exHandler(output, "ant command '" + cmd + "' failed:\n", false) - } - - def runAntTest(file: File): (Boolean, LogContext) = { - val (swr, wr) = newTestWriters() - printInfoStart(file, wr) - - NestUI.verbose(this+" running test "+fileBase) - - val succeeded = try { - val binary = "-Dbinary="+( - if (fileManager.LATEST_LIB endsWith "build/quick/classes/library") "quick" - else if (fileManager.LATEST_LIB endsWith "build/pack/lib/scala-library.jar") "pack" - else if (fileManager.LATEST_LIB endsWith "dists/latest/lib/scala-library.jar/") "latest" - else "installed" - ) - val args = Array(binary, "-logfile", logFile.path, "-file", file.path) - NestUI.verbose("ant "+args.mkString(" ")) - ant(args, logFile) && diffCheck(file, compareOutput(file.getParentFile, logFile)) - } - catch { // *catch-all* - case e: Exception => - NestUI.verbose("caught "+e) - false - } - - (succeeded, LogContext(logFile, swr, wr)) - } - - def runSpecializedTest(file: File): (Boolean, LogContext) = - runTestCommon(file, expectFailure = false)((logFile, outDir) => { - val dir = file.getParentFile - - // adding the instrumented library to the classpath - ( execTest(outDir, logFile, PathSettings.srcSpecLib.toString) && - diffCheck(file, compareOutput(dir, logFile)) - ) - }) - - def runInstrumentedTest(file: File): (Boolean, LogContext) = - runTestCommon(file, expectFailure = false)((logFile, outDir) => { - val dir = file.getParentFile - - // adding the javagent option with path to instrumentation agent - execTest(outDir, logFile, javaOpts = "-javaagent:"+PathSettings.instrumentationAgentLib) && - diffCheck(file, compareOutput(dir, logFile)) - }) - - def processSingleFile(file: File): (Boolean, LogContext) = kind match { - case "scalacheck" => - val succFn: (File, File) => Boolean = { (logFile, outDir) => - NestUI.verbose("compilation of "+file+" succeeded\n") - - val outURL = outDir.getAbsoluteFile.toURI.toURL - val logWriter = new PrintStream(new FileOutputStream(logFile), true) - - Output.withRedirected(logWriter) { - // this classloader is test specific: its parent contains library classes and others - ScalaClassLoader.fromURLs(List(outURL), params.scalaCheckParentClassLoader).run("Test", Nil) - } - - NestUI.verbose(file2String(logFile)) - // obviously this must be improved upon - val lines = SFile(logFile).lines map (_.trim) filterNot (_ == "") toBuffer; - lines.forall(x => !x.startsWith("!")) || { - NestUI.normal("ScalaCheck test failed. Output:\n") - lines foreach (x => NestUI.normal(x + "\n")) - false - } - } - runTestCommon(file, expectFailure = false)( - succFn, - (logFile, outDir) => outputLogFile(logFile) - ) - - case "pos" => - runTestCommon(file, expectFailure = false)( - (logFile, outDir) => true, - (_, _) => () - ) - - case "neg" => - runTestCommon(file, expectFailure = true)((logFile, outDir) => { - // compare log file to check file - val dir = file.getParentFile - - // diff is contents of logFile - fileManager.mapFile(logFile, replaceSlashes(dir, _)) - diffCheck(file, compareOutput(dir, logFile)) - }) - - case "run" | "jvm" => - runJvmTest(file) - - case "specialized" => - runSpecializedTest(file) - - case "instrumented" => - runInstrumentedTest(file) - - case "presentation" => - runJvmTest(file) // for the moment, it's exactly the same as for a run test - - case "ant" => - runAntTest(file) - - case "buildmanager" => - val (swr, wr) = newTestWriters() - printInfoStart(file, wr) - val (outDir, testFile, changesDir) = { - if (!file.isDirectory) - (null, null, null) - else { - NestUI.verbose(this+" running test "+fileBase) - val outDir = createOutputDir() - val testFile = new File(file, fileBase + ".test") - val changesDir = new File(file, fileBase + ".changes") - - if (changesDir.isFile || !testFile.isFile) { - // if changes exists then it has to be a dir - if (!testFile.isFile) NestUI.verbose("invalid build manager test file") - if (changesDir.isFile) NestUI.verbose("invalid build manager changes directory") - (null, null, null) - } - else { - copyTestFiles(file, outDir) - NestUI.verbose("outDir: "+outDir) - NestUI.verbose("logFile: "+logFile) - (outDir, testFile, changesDir) - } - } - } - if (outDir == null) - return (false, LogContext(logFile)) - - // Pre-conditions satisfied - val sourcepath = outDir.getAbsolutePath+File.separator - - // configure input/output files - val logWriter = new PrintStream(new FileOutputStream(logFile), true) - val testReader = new BufferedReader(new FileReader(testFile)) - val logConsoleWriter = new PrintWriter(logWriter, true) - - // create proper settings for the compiler - val settings = new Settings(workerError) - settings.outdir.value = outDir.getAbsoluteFile.getAbsolutePath - settings.sourcepath.value = sourcepath - settings.classpath.value = fileManager.CLASSPATH - settings.Ybuildmanagerdebug.value = true - - // simulate Build Manager loop - val prompt = "builder > " - val reporter = new ConsoleReporter(settings, scala.Console.in, logConsoleWriter) - val bM: BuildManager = - new RefinedBuildManager(settings) { - override protected def newCompiler(settings: Settings) = - new BuilderGlobal(settings, reporter) - } - - def testCompile(line: String): Boolean = { - NestUI.verbose("compiling " + line) - val args = (line split ' ').toList - val command = new CompilerCommand(args, settings) - command.ok && { - bM.update(filesToSet(settings.sourcepath.value, command.files), Set.empty) - !reporter.hasErrors - } - } - - val updateFiles = (line: String) => { - NestUI.verbose("updating " + line) - (line split ' ').toList forall (u => - (u split "=>").toList match { - case origFileName::(newFileName::Nil) => - val newFile = new File(changesDir, newFileName) - if (newFile.isFile) { - val v = overwriteFileWith(new File(outDir, origFileName), newFile) - if (!v) - NestUI.verbose("'update' operation on " + u + " failed") - v - } else { - NestUI.verbose("File " + newFile + " is invalid") - false - } - case a => - NestUI.verbose("Other =: " + a) - false - } - ) - } - - def loop(): Boolean = { - testReader.readLine() match { - case null | "" => - NestUI.verbose("finished") - true - case s if s startsWith ">>update " => - updateFiles(s stripPrefix ">>update ") && loop() - case s if s startsWith ">>compile " => - val files = s stripPrefix ">>compile " - logWriter.println(prompt + files) - // In the end, it can finish with an error - if (testCompile(files)) loop() - else { - val t = testReader.readLine() - (t == null) || (t == "") - } - case s => - NestUI.verbose("wrong command in test file: " + s) - false - } - } - - Output.withRedirected(logWriter) { - try loop() - finally testReader.close() - } - fileManager.mapFile(logFile, replaceSlashes(new File(sourcepath), _)) - - (diffCheck(file, compareOutput(file, logFile)), LogContext(logFile, swr, wr)) - - case "res" => { - // simulate resident compiler loop - val prompt = "\nnsc> " - - val (swr, wr) = newTestWriters() - printInfoStart(file, wr) - - NestUI.verbose(this+" running test "+fileBase) - val dir = file.getParentFile - val outDir = createOutputDir() - val resFile = new File(dir, fileBase + ".res") - NestUI.verbose("outDir: "+outDir) - NestUI.verbose("logFile: "+logFile) - //NestUI.verbose("logFileErr: "+logFileErr) - NestUI.verbose("resFile: "+resFile) - - // run compiler in resident mode - // $SCALAC -d "$os_dstbase".obj -Xresident -sourcepath . "$@" - val sourcedir = logFile.getParentFile.getAbsoluteFile - val sourcepath = sourcedir.getAbsolutePath+File.separator - NestUI.verbose("sourcepath: "+sourcepath) - - val argList = List( - "-d", outDir.getAbsoluteFile.getPath, - "-Xresident", - "-sourcepath", sourcepath) - - // configure input/output files - val logOut = new FileOutputStream(logFile) - val logWriter = new PrintStream(logOut, true) - val resReader = new BufferedReader(new FileReader(resFile)) - val logConsoleWriter = new PrintWriter(new OutputStreamWriter(logOut), true) - - // create compiler - val settings = new Settings(workerError) - settings.sourcepath.value = sourcepath - settings.classpath.value = fileManager.CLASSPATH - val reporter = new ConsoleReporter(settings, scala.Console.in, logConsoleWriter) - val command = new CompilerCommand(argList, settings) - object compiler extends Global(command.settings, reporter) - - val resCompile = (line: String) => { - NestUI.verbose("compiling "+line) - val cmdArgs = (line split ' ').toList map (fs => new File(dir, fs).getAbsolutePath) - NestUI.verbose("cmdArgs: "+cmdArgs) - val sett = new Settings(workerError) - sett.sourcepath.value = sourcepath - val command = new CompilerCommand(cmdArgs, sett) - command.ok && { - (new compiler.Run) compile command.files - !reporter.hasErrors - } - } - - def loop(action: String => Boolean): Boolean = { - logWriter.print(prompt) - resReader.readLine() match { - case null | "" => logWriter.flush() ; true - case line => action(line) && loop(action) - } - } - - Output.withRedirected(logWriter) { - try loop(resCompile) - finally resReader.close() - } - fileManager.mapFile(logFile, replaceSlashes(dir, _)) - - (diffCheck(file, compareOutput(dir, logFile)), LogContext(logFile, swr, wr)) - } - - case "shootout" => - val (swr, wr) = newTestWriters() - printInfoStart(file, wr) - - NestUI.verbose(this+" running test "+fileBase) - val outDir = createOutputDir() - - // 2. define file {outDir}/test.scala that contains code to compile/run - val testFile = new File(outDir, "test.scala") - NestUI.verbose("outDir: "+outDir) - NestUI.verbose("logFile: "+logFile) - NestUI.verbose("testFile: "+testFile) - - // 3. cat {test}.scala.runner {test}.scala > testFile - val runnerFile = new File(parent, fileBase+".scala.runner") - val bodyFile = new File(parent, fileBase+".scala") - SFile(testFile).writeAll( - file2String(runnerFile), - file2String(bodyFile) - ) - - // 4. compile testFile - val ok = compileMgr.attemptCompile(None, List(testFile), kind, logFile) eq CompileSuccess - NestUI.verbose("compilation of " + testFile + (if (ok) "succeeded" else "failed")) - val result = ok && { - execTest(outDir, logFile) && { - NestUI.verbose(this+" finished running "+fileBase) - diffCheck(file, compareOutput(parent, logFile)) - } - } - - (result, LogContext(logFile, swr, wr)) - - case "scalap" => - runInContext(file, (logFile: File, outDir: File) => { - val sourceDir = Directory(if (file.isFile) file.getParent else file) - val sources = sourceDir.files filter (_ hasExtension "scala") map (_.jfile) toList - val results = sourceDir.files filter (_.name == "result.test") map (_.jfile) toList - - if (sources.length != 1 || results.length != 1) { - NestUI.warning("Misconfigured scalap test directory: " + sourceDir + " \n") - false - } - else { - val resFile = results.head - // 2. Compile source file - - if (!compileMgr.attemptCompile(Some(outDir), sources, kind, logFile).isPositive) { - NestUI.normal("compilerMgr failed to compile %s to %s".format(sources mkString ", ", outDir)) - false - } - else { - // 3. Decompile file and compare results - val isPackageObject = sourceDir.name startsWith "package" - val className = sourceDir.name.capitalize + (if (!isPackageObject) "" else ".package") - val url = outDir.toURI.toURL - val loader = ScalaClassLoader.fromURLs(List(url), this.getClass.getClassLoader) - val clazz = loader.loadClass(className) - - val byteCode = ByteCode.forClass(clazz) - val result = scala.tools.scalap.Main.decompileScala(byteCode.bytes, isPackageObject) - - SFile(logFile) writeAll result - diffCheck(file, compareFiles(logFile, resFile)) - } - } - }) - - case "script" => - val (swr, wr) = newTestWriters() - printInfoStart(file, wr) - - NestUI.verbose(this+" running test "+fileBase) - - // check whether there is an args file - val argsFile = new File(file.getParentFile, fileBase+".args") - NestUI.verbose("argsFile: "+argsFile) - val argString = file2String(argsFile) - val succeeded = try { - val cmdString = - if (isWin) { - val batchFile = new File(file.getParentFile, fileBase+".bat") - NestUI.verbose("batchFile: "+batchFile) - batchFile.getAbsolutePath - } - else file.getAbsolutePath - - val ok = runCommand(cmdString+argString, logFile) - ( ok && diffCheck(file, compareOutput(file.getParentFile, logFile)) ) - } - catch { case e: Exception => NestUI.verbose("caught "+e) ; false } - - (succeeded, LogContext(logFile, swr, wr)) - } - - private def crashContext(t: Throwable): LogContext = { - try { - logStackTrace(logFile, t, "Possible compiler crash during test of: " + testFile + "\n") - LogContext(logFile) - } - catch { case t: Throwable => LogContext(null) } - } - - def run(): (Boolean, LogContext) = { - val result = try processSingleFile(testFile) catch { case t: Throwable => (false, crashContext(t)) } - passed = Some(result._1) - result - } - - def reportResult(writers: Option[(StringWriter, PrintWriter)]) { - writers foreach { case (swr, wr) => - if (passed.isEmpty) printInfoTimeout(wr) - else printInfoEnd(passed.get, wr) - wr.flush() - swr.flush() - NestUI.normal(swr.toString) - - if (passed exists (x => !x)) { - if (fileManager.showDiff || isPartestDebug) - NestUI.normal(testDiff) - if (fileManager.showLog) - showLog(logFile) - } - } - toDelete foreach (_.deleteRecursively()) - } - } - - def runTest(testFile: File): TestState = { - val runner = new Runner(testFile) - // when option "--failed" is provided execute test only if log - // is present (which means it failed before) - if (fileManager.failed && !runner.logFile.canRead) - return TestState.Ok - - // sys addShutdownHook cleanup() - val ((success, ctx), elapsed) = timed(runner.run()) - val state = if (success) TestState.Ok else TestState.Fail - - runner.reportResult(ctx.writers) - state - } - - private def filesToSet(pre: String, fs: List[String]): Set[AbstractFile] = - fs flatMap (s => Option(AbstractFile getFile (pre + s))) toSet - - private def copyTestFiles(testDir: File, destDir: File) { - val invalidExts = List("changes", "svn", "obj") - testDir.listFiles.toList filter ( - f => (isJavaOrScala(f) && f.isFile) || - (f.isDirectory && !(invalidExts.contains(SFile(f).extension)))) foreach - { f => fileManager.copyFile(f, destDir) } - } - - private def showLog(logFile: File) { - file2String(logFile) match { - case "" if logFile.canRead => () - case "" => NestUI.failure("Couldn't open log file: " + logFile + "\n") - case s => NestUI.normal(s) - } - } -} diff --git a/src/partest/scala/tools/partest/nest/RunnerUtils.scala b/src/partest/scala/tools/partest/nest/RunnerUtils.scala deleted file mode 100644 index 6707a9338a..0000000000 --- a/src/partest/scala/tools/partest/nest/RunnerUtils.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2013 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -object RunnerUtils { - def splitArgs(str: String) = str split "\\s" filterNot (_ == "") toList - - def searchPath(option: String, as: List[String]): Option[String] = as match { - case `option` :: r :: _ => Some(r) - case _ :: rest => searchPath(option, rest) - case Nil => None - } - - def searchAndRemovePath(option: String, as: List[String]) = (as indexOf option) match { - case -1 => (None, as) - case idx => (Some(as(idx + 1)), (as take idx) ::: (as drop (idx + 2))) - } - - def searchAndRemoveOption(option: String, as: List[String]) = (as indexOf option) match { - case -1 => (false, as) - case idx => (true, (as take idx) ::: (as drop (idx + 1))) - } -} diff --git a/src/partest/scala/tools/partest/nest/SBTRunner.scala b/src/partest/scala/tools/partest/nest/SBTRunner.scala index 20f9c701d5..1cf3aa858f 100644 --- a/src/partest/scala/tools/partest/nest/SBTRunner.scala +++ b/src/partest/scala/tools/partest/nest/SBTRunner.scala @@ -1,3 +1,6 @@ +/* NEST (New Scala Test) + * Copyright 2007-2013 LAMP/EPFL + */ package scala.tools.partest package nest @@ -21,7 +24,7 @@ object SBTRunner extends DirectRunner { val testRootDir: Directory = Directory(testRootPath) } - def reflectiveRunTestsForFiles(kindFiles: Array[File], kind: String):java.util.Map[String, TestState] = { + def reflectiveRunTestsForFiles(kindFiles: Array[File], kind: String): java.util.List[TestState] = { def failedOnlyIfRequired(files:List[File]):List[File]={ if (fileManager.failed) files filter (x => fileManager.logFileExists(x, kind)) else files } @@ -33,7 +36,7 @@ object SBTRunner extends DirectRunner { scalacOptions: Seq[String] = Seq(), justFailedTests: Boolean = false) - def mainReflect(args: Array[String]): java.util.Map[String, String] = { + def mainReflect(args: Array[String]): java.util.List[TestState] = { setProp("partest.debug", "true") val Argument = new scala.util.matching.Regex("-(.*)") @@ -46,7 +49,7 @@ object SBTRunner extends DirectRunner { case x => sys.error("Unknown command line options: " + x) } val config = parseArgs(args, CommandLineOptions()) - fileManager.SCALAC_OPTS ++= config.scalacOptions + fileManager.SCALAC_OPTS = config.scalacOptions fileManager.CLASSPATH = config.classpath getOrElse sys.error("No classpath set") def findClasspath(jar: String, name: String): Option[String] = { @@ -67,22 +70,14 @@ object SBTRunner extends DirectRunner { // TODO - Make this a flag? //fileManager.updateCheck = true // Now run and report... - val runs = config.tests.filterNot(_._2.isEmpty) - (for { - (testType, files) <- runs - (path, result) <- reflectiveRunTestsForFiles(files,testType).asScala - } yield (path, fixResult(result))).seq.asJava - } - def fixResult(result: TestState): String = result match { - case TestState.Ok => "OK" - case TestState.Fail => "FAIL" - case TestState.Timeout => "TIMEOUT" + val runs = config.tests.filterNot(_._2.isEmpty) + val result = runs.toList flatMap { case (kind, files) => reflectiveRunTestsForFiles(files, kind).asScala } + + result.asJava } + def main(args: Array[String]): Unit = { - val failures = ( - for ((path, result) <- mainReflect(args).asScala ; if result != TestState.Ok) yield - path + ( if (result == TestState.Fail) " [FAILED]" else " [TIMEOUT]" ) - ) + val failures = mainReflect(args).asScala collect { case s if !s.isOk => s.longStatus } // Re-list all failures so we can go figure out what went wrong. failures foreach System.err.println if(!failures.isEmpty) sys.exit(1) diff --git a/src/partest/scala/tools/partest/nest/StreamCapture.scala b/src/partest/scala/tools/partest/nest/StreamCapture.scala new file mode 100644 index 0000000000..dc155b1787 --- /dev/null +++ b/src/partest/scala/tools/partest/nest/StreamCapture.scala @@ -0,0 +1,53 @@ +/* NEST (New Scala Test) + * Copyright 2007-2013 LAMP/EPFL + * @author Paul Phillips + */ +package scala.tools.partest +package nest + +import java.io.{ Console => _, _ } + +object StreamCapture { + case class Captured[T](stdout: String, stderr: String, result: T) { + override def toString = s""" + |result: $result + |[stdout] + |$stdout + |[stderr] + |$stderr""".stripMargin.trim + } + + private def mkStream = { + val swr = new StringWriter + val wr = new PrintWriter(swr, true) + val ostream = new PrintStream(new OutputStream { def write(b: Int): Unit = wr write b }, true) // autoFlush = true + + (ostream, () => { ostream.close() ; swr.toString }) + } + + def savingSystem[T](body: => T): T = { + val savedOut = System.out + val savedErr = System.err + try body + finally { + System setErr savedErr + System setOut savedOut + } + } + + def apply[T](body: => T): Captured[T] = { + val (outstream, stdoutFn) = mkStream + val (errstream, stderrFn) = mkStream + + val result = savingSystem { + System setOut outstream + System setErr errstream + Console.withOut(outstream) { + Console.withErr(errstream) { + body + } + } + } + Captured(stdoutFn(), stderrFn(), result) + } +} diff --git a/src/partest/scala/tools/partest/nest/TestFile.scala b/src/partest/scala/tools/partest/nest/TestFile.scala deleted file mode 100644 index 87177772ab..0000000000 --- a/src/partest/scala/tools/partest/nest/TestFile.scala +++ /dev/null @@ -1,81 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2013 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -import java.io.{ File => JFile } -import scala.tools.nsc.Settings -import scala.tools.nsc.util.ClassPath -import scala.tools.nsc.io._ -import scala.util.Properties.{ propIsSet, propOrElse, setProp } - -trait TestFileCommon { - def file: JFile - def kind: String - - val dir = file.toAbsolute.parent - val fileBase = file.stripExtension - val flags = dir / (fileBase + ".flags") ifFile (f => f.slurp().trim) - - lazy val objectDir = dir / (fileBase + "-" + kind + ".obj") createDirectory true - def setOutDirTo = objectDir -} - -abstract class TestFile(val kind: String) extends TestFileCommon { - def file: JFile - def fileManager: FileManager - - def defineSettings(settings: Settings, setOutDir: Boolean) = { - settings.classpath append dir.path - if (setOutDir) - settings.outputDirs setSingleOutput setOutDirTo.path - - // adding codelib.jar to the classpath - // codelib provides the possibility to override standard reify - // this shields the massive amount of reification tests from changes in the API - settings.classpath prepend PathSettings.srcCodeLib.toString - if (propIsSet("java.class.path")) setProp("java.class.path", PathSettings.srcCodeLib.toString + ";" + propOrElse("java.class.path", "")) - - // have to catch bad flags somewhere - (flags forall (f => settings.processArgumentString(f)._1)) && { - settings.classpath append fileManager.CLASSPATH - true - } - } - - override def toString(): String = "%s %s".format(kind, file) -} - -case class PosTestFile(file: JFile, fileManager: FileManager) extends TestFile("pos") -case class NegTestFile(file: JFile, fileManager: FileManager) extends TestFile("neg") -case class RunTestFile(file: JFile, fileManager: FileManager) extends TestFile("run") -case class BuildManagerTestFile(file: JFile, fileManager: FileManager) extends TestFile("bm") -case class ScalaCheckTestFile(file: JFile, fileManager: FileManager) extends TestFile("scalacheck") -case class JvmTestFile(file: JFile, fileManager: FileManager) extends TestFile("jvm") -case class ShootoutTestFile(file: JFile, fileManager: FileManager) extends TestFile("shootout") { - override def setOutDirTo = file.parent -} -case class ScalapTestFile(file: JFile, fileManager: FileManager) extends TestFile("scalap") { - override def setOutDirTo = file.parent -} -case class SpecializedTestFile(file: JFile, fileManager: FileManager) extends TestFile("specialized") { - override def defineSettings(settings: Settings, setOutDir: Boolean): Boolean = { - super.defineSettings(settings, setOutDir) && { - // add the instrumented library version to classpath - settings.classpath prepend PathSettings.srcSpecLib.toString - // @partest maintainer: if we use a custom Scala build (specified via --classpath) - // then the classes provided by it will come earlier than instrumented.jar in the resulting classpath - // this entire classpath business needs a thorough solution - if (propIsSet("java.class.path")) setProp("java.class.path", PathSettings.srcSpecLib.toString + ";" + propOrElse("java.class.path", "")) - true - } - } -} -case class PresentationTestFile(file: JFile, fileManager: FileManager) extends TestFile("presentation") -case class AntTestFile(file: JFile, fileManager: FileManager) extends TestFile("ant") -case class InstrumentedTestFile(file: JFile, fileManager: FileManager) extends TestFile("instrumented") diff --git a/src/partest/scala/tools/partest/package.scala b/src/partest/scala/tools/partest/package.scala index d38ce692d7..5a1afeb77f 100644 --- a/src/partest/scala/tools/partest/package.scala +++ b/src/partest/scala/tools/partest/package.scala @@ -4,35 +4,124 @@ package scala.tools -import java.io.{ FileNotFoundException, File => JFile } -import nsc.io.{ Path, Directory, File => SFile } -import scala.tools.util.PathResolver -import nsc.Properties.{ propOrElse, propOrNone, propOrEmpty } +import java.util.concurrent.{ Callable, ExecutorService } +import scala.concurrent.duration.Duration import scala.sys.process.javaVmArguments -import java.util.concurrent.Callable +import scala.tools.partest.nest.NestUI +import scala.tools.nsc.util.{ ScalaClassLoader, Exceptional } -package partest { - class TestState { - def isOk = this eq TestState.Ok - def isFail = this eq TestState.Fail - def isTimeout = this eq TestState.Timeout +package object partest { + type File = java.io.File + type SFile = scala.reflect.io.File + type Directory = scala.reflect.io.Directory + type Path = scala.reflect.io.Path + type PathResolver = scala.tools.util.PathResolver + type ClassPath[T] = scala.tools.nsc.util.ClassPath[T] + type StringWriter = java.io.StringWriter + + val SFile = scala.reflect.io.File + val Directory = scala.reflect.io.Directory + val Path = scala.reflect.io.Path + val PathResolver = scala.tools.util.PathResolver + val ClassPath = scala.tools.nsc.util.ClassPath + + val space = "\u0020" + val EOL = scala.compat.Platform.EOL + def onull(s: String) = if (s == null) "" else s + def oempty(xs: String*) = xs filterNot (x => x == null || x == "") + def ojoin(xs: String*): String = oempty(xs: _*) mkString space + def nljoin(xs: String*): String = oempty(xs: _*) mkString EOL + + implicit val codec = scala.io.Codec.UTF8 + + def setUncaughtHandler() = { + Thread.setDefaultUncaughtExceptionHandler( + new Thread.UncaughtExceptionHandler { + def uncaughtException(thread: Thread, t: Throwable) { + val t1 = Exceptional unwrap t + System.err.println(s"Uncaught exception on thread $thread: $t1") + t1.printStackTrace() + } + } + ) + } + + /** Sources have a numerical group, specified by name_7 and so on. */ + private val GroupPattern = """.*_(\d+)""".r + + implicit class FileOps(val f: File) { + private def sf = SFile(f) + + def testIdent = { + f.toString split """[/\\]+""" takeRight 2 mkString "/" // e.g. pos/t1234 + } + + def mapInPlace(mapFn: String => String)(filterFn: String => Boolean = _ => true): Unit = + writeAll(fileLines filter filterFn map (x => mapFn(x) + EOL): _*) + + def appendAll(strings: String*): Unit = sf.appendAll(strings: _*) + def writeAll(strings: String*): Unit = sf.writeAll(strings: _*) + def absolutePathSegments: List[String] = f.getAbsolutePath split """[/\\]+""" toList + + def isJava = f.isFile && (sf hasExtension "java") + def isScala = f.isFile && (sf hasExtension "scala") + def isJavaOrScala = isJava || isScala + + def extension = sf.extension + def hasExtension(ext: String) = sf hasExtension ext + def changeExtension(ext: String): File = (sf changeExtension ext).jfile + + /** The group number for this source file, or -1 for no group. */ + def group: Int = + sf.stripExtension match { + case GroupPattern(g) if g.toInt >= 0 => g.toInt + case _ => -1 + } + + def fileContents: String = try sf.slurp() catch { case _: java.io.FileNotFoundException => "" } + def fileLines: List[String] = augmentString(fileContents).lines.toList } - object TestState { - val Ok = new TestState - val Fail = new TestState - val Timeout = new TestState + + implicit class PathOps(p: Path) extends FileOps(p.jfile) { } + + implicit class Copier(val f: SFile) extends AnyVal { + def copyTo(dest: Path): Unit = dest.toFile writeAll f.slurp(scala.io.Codec.UTF8) } -} -package object partest { - import nest.NestUI + implicit class LoaderOps(val loader: ClassLoader) extends AnyVal { + import scala.util.control.Exception.catching + /** Like ScalaClassLoader.create for the case where the result type is + * available to the current class loader, implying that the current + * loader is a parent of `loader`. + */ + def instantiate[A >: Null](name: String): A = ( + catching(classOf[ClassNotFoundException], classOf[SecurityException]) opt + (loader loadClass name).newInstance.asInstanceOf[A] orNull + ) + } - implicit private[partest] def temporaryPath2File(x: Path): JFile = x.jfile - implicit private[partest] def temporaryFile2Path(x: JFile): Path = Path(x) + implicit class ExecutorOps(val executor: ExecutorService) { + def awaitTermination[A](wait: Duration)(failing: => A = ()): Option[A] = ( + if (executor awaitTermination (wait.length, wait.unit)) None + else Some(failing) + ) + } + + implicit def temporaryPath2File(x: Path): File = x.jfile + implicit def stringPathToJavaFile(path: String): File = new File(path) implicit lazy val postfixOps = scala.language.postfixOps implicit lazy val implicitConversions = scala.language.implicitConversions + def fileSeparator = java.io.File.separator + def pathSeparator = java.io.File.pathSeparator + + def pathToTestIdent(path: Path) = path.jfile.testIdent + + def canonicalizeSlashes(line: String) = line.replaceAll("""[/\\]+""", "/") + + def words(s: String): List[String] = (s.trim split "\\s+").toList + def timed[T](body: => T): (T, Long) = { val t1 = System.currentTimeMillis val result = body @@ -43,16 +132,40 @@ package object partest { def callable[T](body: => T): Callable[T] = new Callable[T] { override def call() = body } - def path2String(path: String) = file2String(new JFile(path)) - def file2String(f: JFile) = - try SFile(f).slurp() - catch { case _: FileNotFoundException => "" } + def file2String(f: File): String = f.fileContents def basename(name: String): String = Path(name).stripExtension - def resultsToStatistics(results: Iterable[(_, TestState)]): (Int, Int) = { - val (files, failures) = results map (_._2 == TestState.Ok) partition (_ == true) - (files.size, failures.size) + /** In order to allow for spaces in flags/options, this + * parses .flags, .javaopts, javacopts etc files as follows: + * If it is exactly one line, it is split (naively) on spaces. + * If it contains more than one line, each line is its own + * token, spaces and all. + */ + def readOptionsFile(file: File): List[String] = { + file.fileLines match { + case x :: Nil => words(x) + case xs => xs map (_.trim) + } + } + + def findProgram(name: String): Option[File] = { + val pathDirs = sys.env("PATH") match { + case null => List("/usr/local/bin", "/usr/bin", "/bin") + case path => path split "[:;]" filterNot (_ == "") toList + } + pathDirs.iterator map (d => new File(d, name)) find (_.canExecute) + } + + def now = (new java.util.Date).toString + def elapsedString(millis: Long): String = { + val elapsedSecs = millis/1000 + val elapsedMins = elapsedSecs/60 + val elapsedHrs = elapsedMins/60 + val dispMins = elapsedMins - elapsedHrs * 60 + val dispSecs = elapsedSecs - elapsedMins * 60 + + "%02d:%02d:%02d".format(elapsedHrs, dispMins, dispSecs) } def vmArgString = javaVmArguments.mkString( @@ -67,14 +180,10 @@ package object partest { } def showAllJVMInfo() { - NestUI.verbose(vmArgString) - NestUI.verbose(allPropertiesString) + vlog(vmArgString) + vlog(allPropertiesString) } - def isPartestDebug: Boolean = - propOrEmpty("partest.debug") == "true" - - import scala.language.experimental.macros /** @@ -123,4 +232,10 @@ package object partest { a.tree))))), a.tree)) } + + def isPartestTerse = NestUI.isTerse + def isPartestDebug = NestUI.isDebug + def isPartestVerbose = NestUI.isVerbose + + def vlog(msg: => String) = if (isPartestVerbose) System.err.println(msg) } diff --git a/src/partest/scala/tools/partest/utils/PrintMgr.scala b/src/partest/scala/tools/partest/utils/PrintMgr.scala deleted file mode 100644 index d25be87c1e..0000000000 --- a/src/partest/scala/tools/partest/utils/PrintMgr.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala Parallel Testing ** -** / __/ __// _ | / / / _ | (c) 2007-2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -// $Id$ - -package scala.tools.partest -package utils - -/** - * @author Thomas Hofer - */ -object PrintMgr { - - val NONE = 0 - val SOME = 1 - val MANY = 2 - - var outline = "" - var success = "" - var failure = "" - var warning = "" - var default = "" - - def initialization(number: Int) = number match { - case MANY => - outline = Console.BOLD + Console.BLACK - success = Console.BOLD + Console.GREEN - failure = Console.BOLD + Console.RED - warning = Console.BOLD + Console.YELLOW - default = Console.RESET - case SOME => - outline = Console.BOLD + Console.BLACK - success = Console.RESET - failure = Console.BOLD + Console.BLACK - warning = Console.BOLD + Console.BLACK - default = Console.RESET - case _ => - } - - def printOutline(msg: String) = print(outline + msg + default) - - def printSuccess(msg: String) = print(success + msg + default) - - def printFailure(msg: String) = print(failure + msg + default) - - def printWarning(msg: String) = print(warning + msg + default) -} diff --git a/src/partest/scala/tools/partest/utils/Properties.scala b/src/partest/scala/tools/partest/utils/Properties.scala index 1263c96e9e..b9394b50c9 100644 --- a/src/partest/scala/tools/partest/utils/Properties.scala +++ b/src/partest/scala/tools/partest/utils/Properties.scala @@ -14,4 +14,5 @@ package utils object Properties extends scala.util.PropertiesTrait { protected def propCategory = "partest" protected def pickJarBasedOn = classOf[nest.RunnerManager] + override def isAvian = super.isAvian } |