diff options
Diffstat (limited to 'src/partest')
46 files changed, 2429 insertions, 2739 deletions
diff --git a/src/partest/README b/src/partest/README index 430a2987f8..c7673fe2f8 100644 --- a/src/partest/README +++ b/src/partest/README @@ -1,31 +1,50 @@ -How partest choses the compiler / library: +If you're looking for something to read, I suggest running ../test/partest +with no arguments, which at this moment prints this: - * ''-Dscalatest.build=build/four-pack'' -> will search for libraries in - ''lib'' directory of given path - * ''--pack'' -> will set ''scalatest.build=build/pack'', and run all tests. - add ''--[kind]'' to run a selected set of tests. - * auto detection: - - scalatest.build property -> ''bin'' / ''lib'' directories - - distribution (''dists/latest'') - - supersabbus pack (''build/pack'') - - sabbus quick (''build/quick'') - - installed dist (test files in ''misc/scala-test/files'') +Usage: partest [<options>] [<test> <test> ...] + <test>: a path to a test designator, typically a .scala file or a directory. + Examples: files/pos/test1.scala, files/res/bug785 -How partest choses test files: the test files must be accessible from -the directory on which partest is run. So the test files must be either -at: - * ./test/files - * ./files (cwd is "test") - * ./misc/scala-test/files (installed scala distribution) + Test categories: + --all run all tests (default, unless no options given) + --pos Compile files that are expected to build + --neg Compile files that are expected to fail + --run Test JVM backend + --jvm Test JVM backend + --res Run resident compiler scenarii + --buildmanager Run Build Manager scenarii + --scalacheck Run Scalacheck tests + --script Run script files + --shootout Run shootout tests + --scalap Run scalap tests -Other arguments: - * --pos next files test a compilation success - * --neg next files test a compilation failure - * --run next files test the interpreter and all backends - * --jvm next files test the JVM backend - * --res next files test the resident compiler - * --shootout next files are shootout tests - * --script next files test the script runner - * ''-Dscalatest.scalac_opts=...'' -> add compiler options - * ''--verbose'' -> print verbose messages - * ''-Dpartest.debug=true'' -> print debug messages + Test "smart" categories: + --grep run all tests with a source file containing <expr> + --failed run all tests which failed on the last run + + Specifying paths and additional flags, ~ means repository root: + --rootdir path from ~ to partest (default: test) + --builddir path from ~ to test build (default: build/pack) + --srcdir path from --rootdir to sources (default: files) + --javaopts flags to java on all runs (overrides JAVA_OPTS) + --scalacopts flags to scalac on all tests (overrides SCALAC_OPTS) + --pack alias for --builddir build/pack + --quick alias for --builddir build/quick + + Options influencing output: + --trace show the individual steps taken by each test + --show-diff show diff between log and check file + --show-log show log on failures + --dry-run do not run tests, only show their traces. + --terse be less verbose (almost silent except for failures) + --verbose be more verbose (additive with --trace) + --debug maximum debugging output + --ansi print output in color + + Other options: + --timeout Timeout in seconds + --cleanup delete all stale files and dirs before run + --nocleanup do not delete any logfiles or object dirs + --stats collect and print statistics about the tests + --validate examine test filesystem for inconsistencies + --version print version diff --git a/src/partest/scala/tools/partest/Actions.scala b/src/partest/scala/tools/partest/Actions.scala new file mode 100644 index 0000000000..3e745714cb --- /dev/null +++ b/src/partest/scala/tools/partest/Actions.scala @@ -0,0 +1,168 @@ +/* __ *\ +** ________ ___ / / ___ Scala Parallel Testing ** +** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.tools +package partest + +import util._ +import nsc.io._ + +trait Actions { + partest: Universe => + + class TestSequence(val actions: List[TestStep]) extends AbsTestSequence { + } + + implicit def createSequence(xs: List[TestStep]) = new TestSequence(xs) + + trait ExecSupport { + self: TestEntity => + + def execEnv: Map[String, String] = { + val map = assembleEnvironment() + val cwd = execCwd.toList map ("CWD" -> _.path) + + map ++ cwd + } + def execCwd = if (commandFile.isFile) Some(sourcesDir) else None + + def runExec(args: List[String]): Boolean = { + val cmd = fromArgs(args) + + if (isVerbose) { + trace(execEnv.mkString("ENV(", "\n", "\n)")) + execCwd foreach (x => trace("CWD(" + x + ")")) + } + + trace(cmd) + isDryRun || execAndLog(cmd) + } + + /** Exec a process to run a command. Assumes 0 exit value is success. + * Of necessity, also treats no available exit value as success. + */ + protected def execAndLog(cmd: String): Boolean = { + var proc: Process = null + + val result = interruptMeIn(testTimeout) { + loggingResult { + proc = Process.exec(toArgs(cmd), execEnv, execCwd.orNull, true) + proc.slurp() + } + proc.waitFor() == 0 + } + result getOrElse { + warning("Process never terminated: '%s'" format cmd) + if (proc != null) + proc.destroy() + + false + } + } + } + + trait ScriptableTest { + self: TestEntity => + + // def customTestStep(line: String): TestStep + + /** Translates a line from a .cmds file into a teststep. + */ + def customTestStep(line: String): TestStep = { + val (cmd, rest) = line span (x => !Character.isWhitespace(x)) + val args = toArgs(rest) + def fail: TestStep = (_: TestEntity) => error("Parse error: did not understand '%s'" format line) + + val f: TestEntity => Boolean = cmd match { + case "scalac" => _ scalac args + case "javac" => _ javac args + case "scala" => _ runScala args + case "diff" => if (args.size != 2) fail else _ => diffFiles(File(args(0)), File(args(1))) == "" + case _ => fail + } + f + } + } + + trait CompilableTest extends CompileExecSupport { + self: TestEntity => + + def sourceFiles = location.walk collect { case f: File if isJavaOrScala(f) => f } toList + def allSources = sourceFiles map (_.path) + def scalaSources = sourceFiles filter isScala map (_.path) + def javaSources = sourceFiles filter isJava map (_.path) + + /** If there are mixed java and scala files, the standard compilation + * sequence is: + * + * scalac with all files + * javac with only java files + * scalac with only scala files + * + * This should be expanded to encompass other strategies so we know how + * well they're working or not working - notably, it would be very useful + * to know exactly when and how two-pass compilation fails. + */ + def compile() = { + def compileJava() = javac(javaSources) + def compileScala() = scalac(scalaSources) + def compileAll() = scalac(allSources) + def compileMixed() = compileAll() && compileJava() && compileScala() + + if (scalaSources.nonEmpty && javaSources.nonEmpty) compileMixed() + else compileScala() + } + } + + trait DiffableTest { + self: TestEntity => + + def checkFile: File = withExtension("check").toFile + + def normalizePaths(s: String) = { + /** This accomodates slash/backslash issues by noticing when a given + * line was altered, which means it held a path, and only then converting any + * backslashes to slashes. It's not foolproof but it's as close as we + * can get in one line. + */ + val s2 = s.replaceAll("""(?m)\Q%s\E""" format (sourcesDir + File.separator), "") + if (s != s2) s2.replaceAll("""\\""", "/") else s2 + } + + /** The default cleanup normalizes paths relative to sourcesDir. + */ + def diffCleanup(f: File) = safeLines(f) map normalizePaths mkString "\n" + + /** If optional is true, a missing check file is considered + * a successful diff. Necessary since many categories use + * checkfiles in an ad hoc manner. + */ + def runDiff(check: File, log: File) = { + def arg1 = tracePath(check) + def arg2 = tracePath(log) + def noCheck = !check.exists && returning(true)(_ => trace("diff %s %s [unchecked]".format(arg1, arg2))) + + noCheck || { + def result = safeSlurp(check).trim == diffCleanup(log).trim + def msg = if (result) "passed" else "failed" + + if (isDryRun) { + trace("diff %s %s".format(arg1, arg2)) + true + } + else { + trace("diff %s %s [%s]".format(arg1, arg2, msg)) + result + } + } + } + + private def cleanedLog = returning(File makeTemp "partest-diff")(_ writeAll diffCleanup(logFile)) + def diffOutput(): String = checkFile ifFile (f => diffFiles(f, cleanedLog)) getOrElse "" + } +} diff --git a/src/partest/scala/tools/partest/Alarms.scala b/src/partest/scala/tools/partest/Alarms.scala new file mode 100644 index 0000000000..72afc232e5 --- /dev/null +++ b/src/partest/scala/tools/partest/Alarms.scala @@ -0,0 +1,85 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package partest + +import java.util.{ Timer, TimerTask } + +trait Alarms { + self: Universe => + + def interruptMeIn[T](seconds: Int)(body: => T): Option[T] = { + val thisThread = currentThread + val alarm = new SimpleAlarm(seconds * 1000) set thisThread.interrupt() + + try { Some(body) } + catch { case _: InterruptedException => None } + finally { alarm.cancel() ; Thread.interrupted() } + } + + case class AlarmerAction(secs: Int, action: () => Unit) extends Runnable { + override def run() = action() + } + + /** Set any number of alarms up with tuples of the form: + * seconds to alarm -> Function0[Unit] to execute + */ + class Alarmer(alarms: AlarmerAction*) { + import java.util.concurrent._ + + val exec = Executors.newSingleThreadScheduledExecutor() + alarms foreach (x => exec.schedule(x, x.secs, TimeUnit.SECONDS)) + exec.shutdown() + + def cancelAll() = exec.shutdownNow() + } + + class SimpleAlarm(timeout: Long) { + private val alarm = new Timer + + /** Start a timer, running the given body if it goes off. + */ + def set(body: => Unit) = returning(new TimerTask { def run() = body })(alarm.schedule(_, timeout)) + + /** Cancel the timer. + */ + def cancel() = alarm.cancel() + } + + trait TestAlarms { + test: TestEntity => + + private def warning1 = AlarmerAction(testWarning, () => warning( + """|I've been waiting %s seconds for this to complete: + | %s + |It may be stuck, or if not, it should be broken into smaller tests. + |""".stripMargin.format(testWarning, test)) + ) + private def warning2 = AlarmerAction(testWarning * 2, () => warning( + """|Now I've been waiting %s seconds for this to complete: + | %s + |If partest seems hung it would be a good place to look. + |""".stripMargin.format(testWarning * 2, test)) + ) + + def startAlarms(onTimeout: => Unit) = + if (isNoAlarms) new Alarmer() // for alarm debugging + else new Alarmer(Seq(warning1, warning2, AlarmerAction(testTimeout, () => onTimeout)): _*) + } + + // Thread.setDefaultUncaughtExceptionHandler(new UncaughtException) + // class UncaughtException extends Thread.UncaughtExceptionHandler { + // def uncaughtException(t: Thread, e: Throwable) { + // Console.println("Uncaught in %s: %s".format(t, e)) + // } + // } + // + // lazy val logger = File("/tmp/partest.log").bufferedWriter() + // def flog(msg: String) = logger synchronized { + // logger write (msg + "\n") + // logger.flush() + // } +} diff --git a/src/partest/scala/tools/partest/BuildContributors.scala b/src/partest/scala/tools/partest/BuildContributors.scala new file mode 100644 index 0000000000..64c7e07bc3 --- /dev/null +++ b/src/partest/scala/tools/partest/BuildContributors.scala @@ -0,0 +1,102 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + */ + +package scala.tools +package partest + +import nsc.io._ +import nsc.util.ClassPath + +trait BuildContributors { + universe: Universe => + + /** A trait mixed into types which contribute a portion of the values. + * The basic mechanism is the TestBuild, TestCategory, and TestEntity + * can each contribute to each value. They are assembled at the last + * moment by the ContributorAssembler (presently the TestEntity.) + */ + trait BuildContributor { + def javaFlags: List[String] + def scalacFlags: List[String] + def classpathPaths: List[Path] + def buildProperties: List[(String, Any)] + def buildEnvironment: Map[String, String] + } + + trait ContributorAssembler { + def contributors: List[BuildContributor] + def assemble[T](what: BuildContributor => List[T]): List[T] = contributors flatMap what + + /** !!! This will need work if we want to achieve real composability, + * but it can wait for the demand. + */ + def assembleScalacArgs(args: List[String]) = assemble(_.scalacFlags) ++ args + def assembleJavaArgs(args: List[String]) = assemble(_.javaFlags) ++ args + def assembleProperties() = assemble(_.buildProperties) + def assembleClasspaths(paths: List[Path]) = assemble(_.classpathPaths) ++ paths + def assembleEnvironment() = assemble(_.buildEnvironment.toList).toMap + + def createClasspathString() = ClassPath fromPaths (assembleClasspaths(Nil) : _*) + def createPropertyString() = assembleProperties() map { case (k, v) => "-D%s=%s".format(k, v.toString) } + } + + trait BuildContribution extends BuildContributor { + self: TestBuild => + + /** The base classpath and system properties. + * !!! TODO - this should adjust itself depending on the build + * being tested, because pack and quick at least need different jars. + */ + def classpathPaths = List[Path](library, compiler, partest, fjbg) ++ forkJoinPath + def buildProperties = List( + "scala.home" -> testBuildDir, + "partest.lib" -> library, // used in jvm/inner + "java.awt.headless" -> true, + "user.language" -> "en", + "user.country" -> "US", + "partest.debug" -> isDebug, + "partest.verbose" -> isVerbose + // Disabled because there are no natives tests. + // "java.library.path" -> srcLibDir + ) + def javaFlags: List[String] = toArgs(javaOpts) + def scalacFlags: List[String] = toArgs(scalacOpts) + + /** We put the build being tested's /bin directory in the front of the + * path so the scripts and such written to execute "scala" will use this + * build and not whatever happens to be on their path. + */ + private def modifiedPath = ClassPath.join(scalaBin.path, Properties.envOrElse("PATH", "")) + def buildEnvironment = Map("PATH" -> modifiedPath) + } + + trait CategoryContribution extends BuildContributor { + self: DirBasedCategory => + + /** Category-wide classpath additions placed in <category>/lib. */ + private def libContents = root / "lib" ifDirectory (_.list.toList) + + def classpathPaths = libContents getOrElse Nil + def buildProperties = Nil + def javaFlags = Nil + def scalacFlags = Nil + def buildEnvironment = Map() + } + + trait TestContribution extends BuildContributor with ContributorAssembler { + self: TestEntity => + + def jarsInTestDir = location.walk collect { case f: File if f hasExtension "jar" => f } toList + + def contributors = List(build, category, self) + def javaFlags = safeArgs(javaOptsFile) + def scalacFlags = safeArgs(scalaOptsFile) + def classpathPaths = jarsInTestDir :+ outDir + def buildProperties = List( + "partest.output" -> outDir.toAbsolute, // used in jvm/inner + "partest.cwd" -> outDir.parent.toAbsolute // used in shootout tests + ) + def buildEnvironment = Map("JAVA_OPTS" -> fromArgs(assembleJavaArgs(Nil))) + } +}
\ No newline at end of file diff --git a/src/partest/scala/tools/partest/Categories.scala b/src/partest/scala/tools/partest/Categories.scala new file mode 100644 index 0000000000..1d5a21153f --- /dev/null +++ b/src/partest/scala/tools/partest/Categories.scala @@ -0,0 +1,69 @@ +/* __ *\ +** ________ ___ / / ___ Scala Parallel Testing ** +** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.tools +package partest + +import nsc.Settings +import nsc.io._ +import nsc.util.{ ClassPath } + +trait Categories { + self: Universe => + + trait TestCategory extends AbsTestCategory { + def kind: String + def startMessage: String = "Executing test group" + def testSequence: TestSequence + + class TestSettings(entity: TestEntity, error: String => Unit) extends Settings(error) { + def this(entity: TestEntity) = this(entity, Console println _) + + deprecation.value = false + encoding.value = "ISO-8859-1" + classpath.value = entity.testClasspath + outdir.value = entity.outDir.path + } + + def createSettings(entity: TestEntity): TestSettings = new TestSettings(entity) + def createTest(location: Path): TestEntity = + if (location.isFile) TestFile(this, location.toFile) + else if (location.isDirectory) TestDirectory(this, location.toDirectory) + else error("Failed to create test at '%s'" format location) + + /** Category test identification. + */ + def denotesTestFile(p: Path) = p.isFile && (p hasExtension "scala") + def denotesTestDir(p: Path) = p.isDirectory && !ignorePath(p) + def denotesTest(p: Path) = denotesTestDir(p) || denotesTestFile(p) + + /** This should verify that all necessary files are present. + * By default it delegates to denotesTest. + */ + def denotesValidTest(p: Path) = denotesTest(p) + } + + abstract class DirBasedCategory(val kind: String) extends TestCategory with CategoryContribution { + lazy val root = Directory(src / kind).normalize + def enumerate = root.list filter denotesTest map createTest toList + + /** Standard actions. These can be overridden either on the + * Category level or by individual tests. + */ + def compile: TestStep = (_: TestEntity).compile() + def diff: TestStep = (_: TestEntity).diff() + def run: TestStep = (_: TestEntity).run() + def exec: TestStep = (_: TestEntity).exec() + + /** Combinators. + */ + def not(f: TestStep): TestStep = !f(_: TestEntity) + + override def toString = kind + } +}
\ No newline at end of file diff --git a/src/partest/scala/tools/partest/Compilable.scala b/src/partest/scala/tools/partest/Compilable.scala new file mode 100644 index 0000000000..a1d987ad6d --- /dev/null +++ b/src/partest/scala/tools/partest/Compilable.scala @@ -0,0 +1,103 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + */ + +package scala.tools +package partest + +import scala.tools.nsc.io._ +import scala.tools.nsc.{ Global, Settings, CompilerCommand, FatalError } +import scala.tools.nsc.util.{ ClassPath } +import scala.tools.nsc.reporters.{ Reporter, ConsoleReporter } + +trait PartestCompilation { + self: Universe => + + trait CompileExecSupport extends ExecSupport { + self: TestEntity => + + def javacpArg = "-classpath " + testClasspath + def scalacpArg = "-usejavacp" + + /** Not used, requires tools.jar. + */ + // def javacInternal(args: List[String]) = { + // import com.sun.tools.javac.Main + // Main.compile(args.toArray, logWriter) + // } + + def javac(args: List[String]): Boolean = { + // javac -d outdir -classpath <basepath> <files> + val cmd = "%s -d %s %s %s".format(javacCmd, outDir, javacpArg, fromArgs(args)) + def traceMsg = + if (isVerbose) cmd + else "%s -d %s %s".format(tracePath(Path(javacCmd)), tracePath(outDir), fromArgs(args)) + + trace(traceMsg) + + isDryRun || execAndLog(cmd) + } + + def scalac(args: List[String]): Boolean = { + val allArgs = assembleScalacArgs(args) + val (global, files) = newGlobal(allArgs) + val foundFiles = execCwd match { + case Some(cwd) => files map (x => File(cwd / x)) + case _ => files map (x => File(x)) + } + def nonFileArgs = if (isVerbose) global.settings.recreateArgs else assembleScalacArgs(Nil) + def traceArgs = fromArgs(nonFileArgs ++ (foundFiles map tracePath)) + def traceMsg = + if (isVerbose) "%s %s".format(build.scalaBin / "scalac", traceArgs) + else "scalac " + traceArgs + + trace(traceMsg) + isDryRun || global.partestCompile(foundFiles map (_.path), true) + } + + /** Actually running the test, post compilation. + * Normally args will be List("Test", "jvm"), main class and arg to it. + */ + def runScala(args: List[String]): Boolean = { + val scalaRunnerClass = "scala.tools.nsc.MainGenericRunner" + + // java $JAVA_OPTS <javaopts> -classpath <cp> + val javaCmdAndOptions = javaCmd +: assembleJavaArgs(List(javacpArg)) + // MainGenericRunner -usejavacp <scalacopts> Test jvm + val scalaCmdAndOptions = List(scalaRunnerClass, scalacpArg) ++ assembleScalacArgs(args) + // Assembled + val cmd = fromArgs(javaCmdAndOptions ++ createPropertyString() ++ scalaCmdAndOptions) + + def traceMsg = if (isVerbose) cmd else fromArgs(javaCmd :: args) + trace(traceMsg) + + isDryRun || execAndLog(cmd) + } + + def newReporter(settings: Settings) = new ConsoleReporter(settings, Console.in, logWriter) + + class PartestGlobal(settings: Settings, val creporter: ConsoleReporter) extends Global(settings, creporter) { + def partestCompile(files: List[String], printSummary: Boolean): Boolean = { + try { new Run compile files } + catch { case FatalError(msg) => creporter.error(null, "fatal error: " + msg) } + + if (printSummary) + creporter.printSummary + + creporter.flush() + !creporter.hasErrors + } + } + + def newGlobal(args: List[String]): (PartestGlobal, List[String]) = { + val settings = category createSettings self + val command = new CompilerCommand(args, settings) + val reporter = newReporter(settings) + + if (!command.ok) + debug("Error parsing arguments: '%s'".format(args mkString ", ")) + + (new PartestGlobal(command.settings, reporter), command.files) + } + } +}
\ No newline at end of file diff --git a/src/partest/scala/tools/partest/Config.scala b/src/partest/scala/tools/partest/Config.scala new file mode 100644 index 0000000000..7d8bb80835 --- /dev/null +++ b/src/partest/scala/tools/partest/Config.scala @@ -0,0 +1,130 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + */ + +package scala.tools +package partest + +import io._ +import nsc.io._ +import Properties._ + +trait Config { + universe: Universe => + + lazy val src = absolutize(srcDir).toDirectory + lazy val build = new TestBuild() + + def javaHomeEnv = envOrElse("JAVA_HOME", null) + def javaCmd = envOrElse("JAVACMD", "java") + def javacCmd = Option(javaHomeEnv) map (x => Path(x) / "bin" / "javac" path) getOrElse "javac" + + /** Values related to actors. The timeouts are in seconds. On a dry + * run we only allocate one worker so the output isn't interspersed. + */ + def workerTimeout = 3600 // 1 hour, probably overly generous + def testTimeout = testTimeout_ flatMap safeToInt getOrElse 900 // test timeout + def testWarning = testWarning_ flatMap safeToInt getOrElse (testTimeout / 10) // test warning + def numWorkers = if (isDryRun) 1 else propOrElse("partest.actors", "8").toInt + def expectedErrors = propOrElse("partest.errors", "0").toInt + def poolSize = (wrapAccessControl(propOrNone("actors.corePoolSize")) getOrElse "16").toInt + + def allScalaFiles = src.deepFiles filter (_ hasExtension "scala") + def allObjDirs = src.deepDirs filter (_ hasExtension "obj") + def allLogFiles = src.deepFiles filter (_ hasExtension "log") + def allClassFiles = src.deepFiles filter (_ hasExtension "class") + + class TestBuild() extends BuildContribution { + import nsc.util.ClassPath + + /** Scala core libs. + */ + val library = pathForComponent("library") + val compiler = pathForComponent("compiler") + val partest = pathForComponent("partest") + val scalap = pathForComponent("scalap", "%s.jar") + + /** Scala supplementary libs - these are not all needed for all build targets, + * and some of them are copied inside other jars in later targets. However quick + * for instance cannot be run without some of these. + */ + val fjbg = pathForLibrary("fjbg") + val msil = pathForLibrary("msil") + val forkjoin = pathForLibrary("forkjoin") + val scalacheck = pathForLibrary("scalacheck") + + /** Other interesting paths. + */ + val scalaBin = testBuildDir / "bin" + + /** A hack for now to get quick running. + */ + def needsForkJoin = { + val loader = nsc.util.ScalaClassLoader.fromURLs(List(library.toURL)) + val fjMarker = "scala.concurrent.forkjoin.ForkJoinTask" + val clazz = loader.tryToLoadClass(fjMarker) + + if (clazz.isDefined) debug("Loaded ForkJoinTask OK, don't need jar.") + else debug("Could not load ForkJoinTask, putting jar on classpath.") + + clazz.isEmpty + } + lazy val forkJoinPath: List[Path] = if (needsForkJoin) List(forkjoin) else Nil + + /** Internal **/ + private def repo = partestDir.parent.normalize + // XXX - is this needed? Where? + // + // private val pluginOptionString = "-Xplugin:" + // private def updatedPluginPath(options: String): String = { + // val (pluginArgs, rest) = toArgs(options) partition (_ startsWith pluginOptionString) + // // join all plugin paths as one classpath + // val pluginPaths = ClassPath.join(pluginArgs map (_ stripPrefix pluginOptionString): _*) + // // map all paths to absolute + // val newPath = ClassPath.map(pluginPaths, x => absolutize(x).path) + // // recreate option + // val pluginOption = if (newPath == "") None else Some(pluginOptionString + newPath) + // + // fromArgs(rest ::: pluginOption.toList) + // } + + private def pathForComponent(what: String, jarFormat: String = "scala-%s.jar"): Path = { + def asDir = testBuildDir / "classes" / what + def asJar = testBuildDir / "lib" / jarFormat.format(what) + + if (asDir.isDirectory) asDir + else if (asJar.isFile) asJar + else "" + } + private def pathForLibrary(what: String) = File(repo / "lib" / (what + ".jar")) + } + + def printConfigBanner() = { + debug("Java VM started with arguments: '%s'" format fromArgs(Process.javaVmArguments)) + debug("System Properties:\n" + util.allPropertiesString()) + + normal(configBanner()) + } + + /** Treat an access control failure as None. */ + private def wrapAccessControl[T](body: => Option[T]): Option[T] = + try body catch { case _: java.security.AccessControlException => None } + + private def configBanner() = { + val javaBin = Path(javaHome) / "bin" + val javaInfoString = "%s (build %s, %s)".format(javaVmName, javaVmVersion, javaVmInfo) + + List( + "Scala compiler classes in: " + testBuildDir, + "Scala version is: " + nsc.Properties.versionMsg, + "Scalac options are: " + universe.scalacOpts, + "Java binaries in: " + javaBin, + "Java runtime is: " + javaInfoString, + "Java runtime options: " + (Process.javaVmArguments mkString " "), + "Java options are: " + universe.javaOpts, + "Source directory is: " + src, + "Selected categories: " + (selectedCategories mkString " "), + "" + ) mkString "\n" + } +} diff --git a/src/partest/scala/tools/partest/Dispatcher.scala b/src/partest/scala/tools/partest/Dispatcher.scala new file mode 100644 index 0000000000..2c7d9d6a2f --- /dev/null +++ b/src/partest/scala/tools/partest/Dispatcher.scala @@ -0,0 +1,161 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Philipp Haller + */ + +package scala.tools +package partest + +import scala.tools.nsc.io._ +import scala.actors.{ Actor, TIMEOUT } +import scala.actors.Actor._ +import scala.collection.immutable +import scala.util.control.Exception.ultimately + +/** The machinery for concurrent execution of tests. Each Worker + * is given a bundle of tests, which it runs sequentially and then + * sends a report back to the dispatcher. + */ +trait Dispatcher { + partest: Universe => + + /** The public entry point. The given filter narrows down the list of + * tests to run. + */ + def runSelection(categories: List[TestCategory], filt: TestEntity => Boolean = _ => true): CombinedTestResults = { + // Setting scala.home informs tests where to obtain their jars. + setProp("scala.home", testBuildDir.path) + + val allTests = allCategories flatMap (_.enumerate) + val selected = allTests filter filt + val groups = selected groupBy (_.category) + val count = selected.size + + if (count == 0) return CombinedTestResults(0, 0, 0) + else if (count == allTests.size) verbose("Running all %d tests." format count) + else verbose("Running %d/%d tests: %s".format(count, allTests.size, toStringTrunc(selected map (_.label) mkString ", "))) + + allCategories collect { case x if groups contains x => runCategory(x, groups(x)) } reduceLeft (_ ++ _) + } + + private def parallelizeTests(tests: List[TestEntity]): immutable.Map[TestEntity, Int] = { + // propagate verbosity + if (isDebug) scala.actors.Debug.level = 3 + + // "If elected, I guarantee a slice of tests for every worker!" + val groups = tests grouped ((tests.size / numWorkers) + 1) toList + + // "Workers, line up for assignments!" + val workers = + for ((slice, workerNum) <- groups.zipWithIndex) yield { + returning(new Worker(workerNum)) { worker => + worker.start() + worker ! TestsToRun(slice) + } + } + + normal("Started %d workers with ~%d tests each.\n".format(groups.size, groups.head.size)) + + /** Listening for news from the proletariat. + */ + (workers map { w => + receiveWithin(workerTimeout * 1000) { + case ResultsOfRun(resultMap) => resultMap + case TIMEOUT => + warning("Worker %d timed out." format w.workerNum) + immutable.Map[TestEntity, Int]() + // mark all the worker's tests as having timed out - should be hard to miss + groups(w.workerNum) map (_ -> 2) toMap + } + }) reduceLeft (_ ++ _) + } + + private def runCategory(category: TestCategory, tests: List[TestEntity]): CombinedTestResults = { + val kind = category.kind + normal("%s (%s tests in %s)\n".format(category.startMessage, tests.size, category)) + + val (milliSeconds, resultMap) = timed2(parallelizeTests(tests)) + val (passed, failed) = resultsToStatistics(resultMap) + + CombinedTestResults(passed, failed, milliSeconds) + } + + /** A Worker is given a bundle of tests and runs them all sequentially. + */ + class Worker(val workerNum: Int) extends Actor { + def act() { + react { case TestsToRun(tests) => + val master = sender + runTests(tests)(results => master ! ResultsOfRun(results)) + } + } + + /** Runs the tests. Passes the result Map to onCompletion when done. + */ + private def runTests(tests: List[TestEntity])(onCompletion: immutable.Map[TestEntity, Int] => Unit) { + var results = new immutable.HashMap[TestEntity, Int] // maps tests to results + val numberOfTests = tests.size + val testIterator = tests.iterator + def processed = results.size + def isComplete = testIterator.isEmpty + + def atThreshold(num: Double) = { + require(num >= 0 && num <= 1.0) + ((processed - 1).toDouble / numberOfTests <= num) && (processed.toDouble / numberOfTests >= num) + } + + def extraMessage = { + // for now quiet for normal people + if (isVerbose || isTrace || isDebug) { + if (isComplete) "(#%d 100%%)" format workerNum + else if (isVerbose) "(#%d %d/%d)".format(workerNum, processed, numberOfTests) + else if (isTrace && atThreshold(0.5)) "(#%d 50%%)" format workerNum + else "" + } + else "" + } + + def countAndReport(result: TestResult) { + val TestResult(test, state) = result + // refuse to count an entity twice + if (results contains test) + return warning("Received duplicate result for %s: was %s, now %s".format(test, results(test), state)) + + // increment the counter for this result state + results += (test -> state) + + // show on screen + if (isDryRun) normal("\n") // blank line between dry run traces + else result show extraMessage + + // remove log if successful + if (result.passed) + test.deleteLog() + + // Respond to master if this Worker is complete + if (isComplete) + onCompletion(results) + } + + Actor.loopWhile(testIterator.hasNext) { + val parent = self + // pick a test and set some alarms + val test = testIterator.next + val alarmer = test startAlarms (parent ! new Timeout(test)) + + actor { + ultimately(alarmer.cancelAll()) { + // Calling isSuccess forces the lazy val "process" inside the test, running it. + val res = test.isSuccess + // Cancel the alarms and alert the media. + parent ! TestResult(test, res) + } + } + + react { + case x: TestResult => countAndReport(x) + } + } + } + } +}
\ No newline at end of file diff --git a/src/partest/scala/tools/partest/Entities.scala b/src/partest/scala/tools/partest/Entities.scala new file mode 100644 index 0000000000..658cfdee12 --- /dev/null +++ b/src/partest/scala/tools/partest/Entities.scala @@ -0,0 +1,77 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Philipp Haller + */ + +package scala.tools +package partest + +import nsc.io._ + +trait Entities { + self: Universe => + + abstract class TestEntity extends AbsTestEntity + with TestContribution + with TestHousekeeping + with TestAlarms + with EntityLogging + with CompilableTest + with ScriptableTest + with DiffableTest { + def location: Path + def category: TestCategory + + lazy val label = location.stripExtension + lazy val testClasspath = returning(createClasspathString())(vtrace) + + /** Was this test successful? Calling this for the first time forces + * lazy val "process" which actually runs the test. + */ + def isSuccess = process + + /** Some standard files, which may or may not be present. + */ + def scalaOptsFile = withExtension("flags").toFile // opts to scalac + def javaOptsFile = withExtension("javaopts").toFile // opts to java (but not javac) + def commandFile = withExtension("cmds").toFile // sequence of commands to execute + def logFile = withExtension("log").toFile // collected output + + /** Some standard directories. + */ + def outDir = withExtension("obj").toDirectory // output dir, e.g. files/pos/t14.obj + def categoryDir = location.parent.normalize // category dir, e.g. files/pos/ + def sourcesDir = location ifDirectory (_.normalize) getOrElse categoryDir + + /** Standard arguments for run, exec, diff. + */ + def argumentsToRun = List("Test", "jvm") + def argumentsToExec = List(location.path) + def argumentsToDiff = ((checkFile, logFile)) + + /** Using a .cmds file for a custom test sequence. + */ + def commandList = safeLines(commandFile) + def testSequence = + if (commandFile.isFile && commandList.nonEmpty) commandList map customTestStep + else category.testSequence + + def run() = runScala(argumentsToRun) + def exec() = runExec(argumentsToExec) + def diff() = runDiff(argumentsToDiff._1, argumentsToDiff._2) + + /** The memoized result of the test run. + */ + private lazy val process = { + def preCheck = precondition || returning(false)(_ => trace("precondition failed")) + def allSteps = testSequence.actions forall (f => f(this)) + val outcome = runWrappers(preCheck && allSteps) + + // an empty outcome means we've been interrupted and are shutting down. + outcome getOrElse false + } + } + + case class TestDirectory(category: TestCategory, location: Directory) extends TestEntity { } + case class TestFile(category: TestCategory, location: File) extends TestEntity { } +} diff --git a/src/partest/scala/tools/partest/Housekeeping.scala b/src/partest/scala/tools/partest/Housekeeping.scala new file mode 100644 index 0000000000..a624ca8adb --- /dev/null +++ b/src/partest/scala/tools/partest/Housekeeping.scala @@ -0,0 +1,187 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + */ + +package scala.tools +package partest + +import scala.util.control.Exception.catching +import util._ +import nsc.io._ +import Process.runtime +import Properties._ + +/** An agglomeration of code which is low on thrills. Hopefully + * it operates so quietly in the background that you never have to + * look at this file. + */ +trait Housekeeping { + self: Universe => + + /** Orderly shutdown on ctrl-C. */ + @volatile private var _shuttingDown = false + protected def setShuttingDown() = { + /** Whatever we want to do as shutdown begins goes here. */ + if (!_shuttingDown) { + warning("Received shutdown signal, partest is cleaning up...\n") + _shuttingDown = true + } + } + def isShuttingDown = _shuttingDown + + /** Execute some code with a shutdown hook in place. This is + * motivated by the desire not to leave the filesystem full of + * junk when someone ctrl-Cs a test run. + */ + def withShutdownHook[T](hook: => Unit)(body: => T): Option[T] = + /** Java doesn't like it if you keep adding and removing shutdown + * hooks after shutdown has begun, so we trap the failure. + */ + catching(classOf[IllegalStateException]) opt { + val t = new Thread() { + override def run() = { + setShuttingDown() + hook + } + } + runtime addShutdownHook t + + try body + finally runtime removeShutdownHook t + } + + /** Search for a directory, possibly given only a name, by starting + * at the current dir and walking upward looking for it at each level. + */ + protected def searchForDir(name: String): Directory = { + val result = Path(name) ifDirectory (x => x.normalize) orElse { + val cwd = Directory.Current getOrElse error("user.dir property not set") + val dirs = cwd :: cwd.parents map (_ / name) + + Path onlyDirs dirs map (_.normalize) headOption + } + + result getOrElse error("Fatal: could not find directory '%s'" format name) + } + + /** Paths we ignore for most purposes. + */ + def ignorePath(x: Path) = { + (x.name startsWith ".") || + (x.isDirectory && ((x.name == "lib") || x.hasExtension("obj", "svn"))) + } + /** Make a possibly relative path absolute using partestDir as the base. + */ + def absolutize(path: String) = Path(path) toAbsoluteWithRoot partestDir + + /** Go on a deleting binge. + */ + def cleanupAll() { + if (isNoCleanup) + return + + val (dirCount, fileCount) = (cleanupObjDirs(), cleanupLogs() + cleanupJunk()) + if (dirCount + fileCount > 0) + normal("Cleaned up %d directories and %d files.\n".format(dirCount, fileCount)) + } + + def cleanupObjDirs() = countTrue(allObjDirs collect { case x if x.exists => x.deleteRecursively() }) + def cleanupJunk() = countTrue(allClassFiles collect { case x if x.exists => x.delete() }) + def cleanupLogs() = countTrue(allLogFiles collect { case x if x.exists => x.delete() }) + + /** Look through every file in the partest directory and ask around + * to make sure someone knows him. Complain about strangers. + */ + def validateAll() { + def denotesTest(p: Path) = allCategories exists (_ denotesTest p) + def isMSILcheck(p: Path) = p.name endsWith "-msil.check" + + def analyzeCategory(cat: DirBasedCategory) = { + val allTests = cat.enumerate + val otherPaths = cat.root walkFilter (x => !ignorePath(x)) filterNot (cat denotesTest _) filterNot isMSILcheck toList + val count = otherPaths.size + + println("Validating %d non-test paths in %s.".format(count, cat.kind)) + + for (path <- otherPaths) { + (allTests find (_ acknowledges path)) match { + case Some(test) => if (isVerbose) println(" OK: '%s' is claimed by '%s'".format(path, test.label)) + case _ => println(">> Unknown path '%s'" format path) + } + } + } + + allCategories collect { case x: DirBasedCategory => analyzeCategory(x) } + } + + trait TestHousekeeping { + self: TestEntity => + + /** Calculating derived files. Given a test like + * files/run/foo.scala or files/run/foo/ + * This creates paths like foo.check, foo.flags, etc. + */ + def withExtension(extension: String) = categoryDir / "%s.%s".format(label, extension) + + /** True for a path if this test acknowledges it belongs to this test. + * Overridden by some categories. + */ + def acknowledges(path: Path): Boolean = { + val loc = location.normalize + val knownPaths = List(scalaOptsFile, javaOptsFile, commandFile, logFile, checkFile) ++ jarsInTestDir + def isContainedSource = location.isDirectory && isJavaOrScala(path) && (path.normalize startsWith loc) + + (knownPaths exists (_ isSame path)) || isContainedSource + } + + /** This test "responds to" this String. This could mean anything -- it's a + * way of specifying ad-hoc collections of tests to exercise only a subset of tests. + * At present it looks for the given String in all the test sources. + */ + def respondsToString(str: String) = containsString(str) + def containsString(str: String) = { + debug("Checking %s for \"%s\"".format(sourceFiles mkString ", ", str)) + sourceFiles map safeSlurp exists (_ contains str) + } + + def possiblyTimed[T](body: => T): T = { + if (isStats) timed(recordTestTiming(label, _))(body) + else body + } + + private def prepareForTestRun() = { + // make sure we have a clean slate + deleteLog(force = true) + if (outDir.exists) + outDir.deleteRecursively() + + // recreate object dir + outDir createDirectory true + } + def deleteOutDir() = outDir.deleteRecursively() + def deleteShutdownHook() = { debug("Shutdown hook deleting " + outDir) ; deleteOutDir() } + + protected def runWrappers[T](body: => T): Option[T] = { + prepareForTestRun() + + withShutdownHook(deleteShutdownHook()) { + loggingOutAndErr { + val result = possiblyTimed { body } + if (!isNoCleanup) + deleteOutDir() + + result + } + } + } + + override def toString = location.path + override def equals(other: Any) = other match { + case x: TestEntity => location.normalize == x.location.normalize + case _ => false + } + override def hashCode = location.normalize.hashCode + } + + private def countTrue(f: => Iterator[Boolean]) = f filter (_ == true) length +}
\ No newline at end of file diff --git a/src/partest/scala/tools/partest/Partest.scala b/src/partest/scala/tools/partest/Partest.scala new file mode 100644 index 0000000000..019ed270e5 --- /dev/null +++ b/src/partest/scala/tools/partest/Partest.scala @@ -0,0 +1,72 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + */ + +package scala.tools +package partest + +import nsc.io._ +import nsc.util.CommandLine +import category.AllCategories + +/** Global object for a Partest run. It is completely configured by the list + * of arguments passed to the constructor (although there are a few properties + * and environment variables which can influence matters.) See PartestSpec.scala + * for the complete list. + */ +class Partest(args: List[String]) extends { + val parsed = PartestSpecReference(args: _*) +} with Universe with PartestSpec with AllCategories { + + debug("Partest object created with args: " + (args mkString " ")) + + // The abstract values from Universe. + lazy val testBuildDir = searchForDir(buildDir) + lazy val partestDir = searchForDir(rootDir) + lazy val allCategories = List(Pos, Neg, Run, Jvm, Res, Shootout, Scalap, Scalacheck, BuildManager, Script) + + lazy val selectedCategories = if (isAllImplied) allCategories else specifiedCats + + // Coarse validation of partest directory: holds a file called partest. + (partestDir / "partest").isFile || error("'%s' is not a valid partest directory." format partestDir) + + def runSets = toArgs(parsed.getOrElse("--runsets", "")) + def specifiedTests = parsed.residualArgs map (x => Path(x).normalize) + def specifiedKinds = testKinds filter (x => isSet(x) || (runSets contains x)) + def specifiedCats = specifiedKinds flatMap (x => allCategories find (_.kind == x)) + def isAllImplied = isAll || (specifiedTests.isEmpty && specifiedKinds.isEmpty) + + /** Assembles a filter based on command line options which restrict the test set + * --grep limits to only matching tests + * --failed limits to only recently failed tests (log file is present) + * --<category> limits to only the given tests and categories (but --all overrides) + * path/to/Test limits to only the given tests and categories + */ + lazy val filter = { + def indivFilter(test: TestEntity) = specifiedTests contains test.location.normalize + def categoryFilter(test: TestEntity) = specifiedCats contains test.category + def indivOrCat(test: TestEntity) = isAllImplied || indivFilter(test) || categoryFilter(test) // combines previous two + + def failedFilter(test: TestEntity) = !isFailed || (test.logFile exists) + def grepFilter(test: TestEntity) = grepExpr.isEmpty || (test containsString grepExpr.get) + def combinedFilter(x: TestEntity) = indivOrCat(x) && failedFilter(x) && grepFilter(x) // combines previous three + + combinedFilter _ + } + + def launchTestSuite() = runSelection(selectedCategories, filter) +} + +object Partest { + def fromBuild(dir: String, args: String*): Partest = apply("--builddir" +: dir +: args: _*) + def apply(args: String*): Partest = new Partest(args.toList) + + // builds without partest jars won't actually work + def starr() = fromBuild("") + def locker() = fromBuild("build/locker") + def quick() = fromBuild("build/quick") + def pack() = fromBuild("build/pack") + def strap() = fromBuild("build/strap") + def dist() = fromBuild("dists/latest") +} + diff --git a/src/partest/scala/tools/partest/PartestSpec.scala b/src/partest/scala/tools/partest/PartestSpec.scala new file mode 100644 index 0000000000..a8a1d9b0cb --- /dev/null +++ b/src/partest/scala/tools/partest/PartestSpec.scala @@ -0,0 +1,108 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package partest + +import Properties._ +import nsc.io._ +import nsc.util.{ CommandLine, CommandLineSpec, CommandLineReferenceSpec } + +/** This takes advantage of bits of scala goodness to fully define a command + * line program with a minimum of duplicated code. When the specification object + * is created, the vals are evaluated in order and each of them side effects + * a private accumulator. What emerges is a full list of the valid unary + * and binary arguments, as well as autogenerated help. + */ +trait PartestSpec extends CommandLineSpec { + override def isPassthroughProperty(key: String) = key == "partest.options" + override def isSysPropOption(key: String) = { + val segments = (key split '.').toList + if (segments.size == 2 && segments.head == "partest") Some(segments.last) + else None + } + + private var _testKinds: List[String] = Nil + private def kind(s: String) = returning(s)(_testKinds +:= _) + + def testKinds = _testKinds + def versionMsg = Properties.versionMsg + + help(""" + |Usage: partest [<options>] [<test> <test> ...] + | <test>: a path to a test designator, typically a .scala file or a directory. + | Examples: files/pos/test1.scala, files/res/bug785""") + + heading ("Test categories:") + val isAll = ("all" / "run all tests (default, unless no options given)" ?) + (kind("pos") / "Compile files that are expected to build" ?) + (kind("neg") / "Compile files that are expected to fail" ?) + (kind("run") / "Test JVM backend" ?) + (kind("jvm") / "Test JVM backend" ?) + (kind("res") / "Run resident compiler scenarii" ?) + (kind("buildmanager") / "Run Build Manager scenarii" ?) + (kind("scalacheck") / "Run Scalacheck tests" ?) + (kind("script") / "Run script files" ?) + (kind("shootout") / "Run shootout tests" ?) + (kind("scalap") / "Run scalap tests" ?) + + heading ("""Test "smart" categories:""") + val grepExpr = "grep" / "run all tests with a source file containing <expr>" >> + val isFailed = "failed" / "run all tests which failed on the last run" ? + + heading ("Specifying paths and additional flags, ~ means repository root:") + val rootDir = "rootdir" / "path from ~ to partest (default: test)" |> "test" + val buildDir = "builddir" / "path from ~ to test build (default: build/pack)" |> "build/pack" + val srcDir = "srcdir" / "path from --rootdir to sources (default: files)" |> "files" + val javaOpts = "javaopts" / "flags to java on all runs (overrides JAVA_OPTS)" |> envOrElse("JAVA_OPTS", "") + val scalacOpts = "scalacopts" / "flags to scalac on all tests (overrides SCALAC_OPTS)" |> envOrElse("SCALAC_OPTS", "") + + ("pack" / "alias for --builddir build/pack") ?+> List("--builddir", "build/pack") + ("quick" / "alias for --builddir build/quick") ?+> List("--builddir", "build/quick") + + heading ("Options influencing output:") + val isTrace = "trace" / "show the individual steps taken by each test" ? + val isShowDiff = "show-diff" / "show diff between log and check file" ? + val isShowLog = "show-log" / "show log on failures" ? + val isDryRun = "dry-run" / "do not run tests, only show their traces." ? + val isTerse = "terse" / "be less verbose (almost silent except for failures)" ? + val isVerbose = "verbose" / "be more verbose (additive with --trace)" ? + val isDebug = "debug" / "maximum debugging output" ? + val isAnsi = "ansi" / "print output in color" ? + + heading ("Other options:") + val timeout_ = "timeout" / "Overall timeout in seconds" |> "14400" + val testWarning_ = "test-warning" / "Test warning in seconds" >> ; // defaults to testTimeout / 10 + val testTimeout_ = "test-timeout" / "Test timeout in seconds" >> ; // defaults to 900 + val isCleanup = "cleanup" / "delete all stale files and dirs before run" ? + val isNoCleanup = "nocleanup" / "do not delete any logfiles or object dirs" ? + val isStats = "stats" / "collect and print statistics about the tests" ? + val isValidate = "validate" / "examine test filesystem for inconsistencies" ? + val isVersion = "version" / "print version" ? + + // no help for anything below this line - secret options + // mostly intended for property configuration. + val runsets = "runsets" |> "" + val isNoAlarms = ("noalarms" ?) + val isInsideAnt = ("is-in-ant" ?) +} + +object PartestSpecReference extends PartestSpec with CommandLineReferenceSpec { + import CommandLineSpec._ + + def parsed: CommandLine = null + override def creator(args: List[String]) = + new ThisCommandLine(args) { + override def onlyKnownOptions = true + override def errorFn(msg: String) = printAndExit("Error: " + msg) + } + + def main(args: Array[String]): Unit = println(bashCompletion("partest")) + + /** Append bash completion for partest to the given file. + */ + def appendCompletionTo(f: File) = f appendAll bashCompletion("partest") +} + diff --git a/src/partest/scala/tools/partest/PartestTask.scala b/src/partest/scala/tools/partest/PartestTask.scala deleted file mode 100644 index 59781b0aa2..0000000000 --- a/src/partest/scala/tools/partest/PartestTask.scala +++ /dev/null @@ -1,250 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala Parallel Testing ** -** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -// $Id$ - -package scala.tools.partest - -import scala.actors.Actor._ - -import java.io.File -import java.net.URLClassLoader - -import org.apache.tools.ant.Task -import org.apache.tools.ant.types.{Path, Reference, FileSet} - -class PartestTask extends Task { - - 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 addConfiguredScriptTests(input: FileSet) { - scriptFiles = Some(input) - } - - def addConfiguredShootoutTests(input: FileSet) { - shootoutFiles = Some(input) - } - - def addConfiguredScalapTests(input: FileSet) { - scalapFiles = Some(input) - } - - def setClasspath(input: Path) { - if (classpath.isEmpty) - classpath = Some(input) - else - classpath.get.append(input) - } - - def createClasspath(): Path = { - if (classpath.isEmpty) classpath = Some(new Path(getProject())) - classpath.get.createPath() - } - - 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 - } - - def setJavaCmd(input: File) { - javacmd = Some(input) - } - - def setJavacCmd(input: File) { - javaccmd = Some(input) - } - - def setScalacOpts(opts: String) { - scalacOpts = Some(opts) - } - - def setTimeout(delay: String) { - timeout = Some(delay) - } - - def setDebug(input: Boolean) { - debug = input - } - - private var classpath: Option[Path] = 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 scriptFiles: Option[FileSet] = None - private var shootoutFiles: Option[FileSet] = None - private var scalapFiles: Option[FileSet] = None - private var errorOnFailed: Boolean = false - private var scalacOpts: Option[String] = None - private var timeout: Option[String] = None - private var debug = false - - private def getFiles(fileSet: Option[FileSet]): Array[File] = - if (fileSet.isEmpty) Array() - else { - val files = fileSet.get - files.getDirectoryScanner(getProject).getIncludedFiles map { - fs => new File(files.getDir(getProject), fs) - } - } - - private def getFilesAndDirs(fileSet: Option[FileSet]): Array[File] = - if (!fileSet.isEmpty) { - val files = fileSet.get - val fileTests = getFiles(fileSet) - val dir = files.getDir(getProject) - val dirTests = dir.listFiles(new java.io.FileFilter { - def accept(file: File) = - file.isDirectory && - (!file.getName().equals(".svn")) && - (!file.getName().endsWith(".obj")) - }) - (dirTests ++ fileTests).toArray - } - else - Array() - - private def getPosFiles = getFilesAndDirs(posFiles) - private def getNegFiles = getFilesAndDirs(negFiles) - private def getRunFiles = getFiles(runFiles) - private def getJvmFiles = getFilesAndDirs(jvmFiles) - private def getResidentFiles = getFiles(residentFiles) - private def getScriptFiles = getFiles(scriptFiles) - private def getShootoutFiles = getFiles(shootoutFiles) - private def getScalapFiles = getFiles(scalapFiles) - - override def execute() { - if (debug) - System.setProperty("partest.debug", "true") - - if (classpath.isEmpty) - error("Mandatory attribute 'classpath' is not set.") - - val scalaLibrary = - (classpath.get.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 - } - } - - if (scalaLibrary.isEmpty) - error("Provided classpath does not contain a Scala library.") - - val classloader = this.getClass.getClassLoader - - val antRunner: AnyRef = - classloader.loadClass("scala.tools.partest.nest.AntRunner").newInstance().asInstanceOf[AnyRef] - val antFileManager: AnyRef = - antRunner.getClass.getMethod("fileManager", Array[Class[_]](): _*).invoke(antRunner, Array[Object](): _*) - - val runMethod = - antRunner.getClass.getMethod("reflectiveRunTestsForFiles", Array(classOf[Array[File]], classOf[String]): _*) - - def runTestsForFiles(kindFiles: Array[File], kind: String): (Int, Int) = { - val result = runMethod.invoke(antRunner, Array(kindFiles, kind): _*).asInstanceOf[Int] - (result >> 16, result & 0x00FF) - } - - def setFileManagerBooleanProperty(name: String, value: Boolean) { - val setMethod = - antFileManager.getClass.getMethod(name+"_$eq", Array(classOf[Boolean]): _*) - setMethod.invoke(antFileManager, Array(java.lang.Boolean.valueOf(value)).asInstanceOf[Array[Object]]: _*) - } - - def setFileManagerStringProperty(name: String, value: String) { - val setMethod = - antFileManager.getClass.getMethod(name+"_$eq", Array(classOf[String]): _*) - setMethod.invoke(antFileManager, Array(value).asInstanceOf[Array[Object]]: _*) - } - - setFileManagerBooleanProperty("showDiff", showDiff) - setFileManagerBooleanProperty("showLog", showLog) - setFileManagerBooleanProperty("failed", runFailed) - if (!javacmd.isEmpty) - setFileManagerStringProperty("JAVACMD", javacmd.get.getAbsolutePath) - if (!javaccmd.isEmpty) - setFileManagerStringProperty("JAVAC_CMD", javaccmd.get.getAbsolutePath) - setFileManagerStringProperty("CLASSPATH", classpath.get.list.mkString(File.pathSeparator)) - setFileManagerStringProperty("LATEST_LIB", scalaLibrary.get.getAbsolutePath) - if (!scalacOpts.isEmpty) - setFileManagerStringProperty("SCALAC_OPTS", scalacOpts.get) - if (!timeout.isEmpty) - setFileManagerStringProperty("timeout", timeout.get) - - 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"), - (getScriptFiles, "script", "Running script files"), - (getShootoutFiles, "shootout", "Running shootout tests"), - (getScalapFiles, "scalap", "Running scalap tests") - ) - - def runSet(set: TFSet): (Int, Int) = { - val (files, name, msg) = set - if (files.isEmpty) (0, 0) - else { - log(msg) - runTestsForFiles(files, name) - } - } - - val _results = testFileSets map runSet - val allSuccesses = _results map (_._1) sum - val allFailures = _results map (_._2) sum - - def f = if (errorOnFailed && allFailures > 0) error(_) else log(_: String) - def s = if (allFailures > 1) "s" else "" - val msg = - if (allFailures > 0) "Test suite finished with %d case%s failing.".format(allFailures, s) - else if (allSuccesses == 0) "There were no tests to run." - else "Test suite finished with no failures." - - f(msg) - } -} diff --git a/src/partest/scala/tools/partest/utils/Properties.scala b/src/partest/scala/tools/partest/Properties.scala index 237ddea14e..4eeb0359ec 100644 --- a/src/partest/scala/tools/partest/utils/Properties.scala +++ b/src/partest/scala/tools/partest/Properties.scala @@ -8,8 +8,8 @@ // $Id$ -package scala.tools.partest -package utils +package scala.tools +package partest /** Loads partest.properties from the jar. */ object Properties extends scala.util.PropertiesTrait { diff --git a/src/partest/scala/tools/partest/Results.scala b/src/partest/scala/tools/partest/Results.scala new file mode 100644 index 0000000000..4e0c446788 --- /dev/null +++ b/src/partest/scala/tools/partest/Results.scala @@ -0,0 +1,113 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + */ + +package scala.tools +package partest + +import scala.collection.immutable + +trait Results { + self: Universe => + + /** A collection of tests for a Worker. + */ + case class TestsToRun(entities: List[TestEntity]) + + /** The response from a Worker who has been given TestsToRun. + */ + case class ResultsOfRun(results: immutable.Map[TestEntity, Int]) + + /** The result of a single test. (0: OK, 1: FAILED, 2: TIMEOUT) + */ + sealed abstract class TestResult(val state: Int, val description: String) { + def entity: TestEntity + + def passed = state == 0 + def colorize(s: String): String + def show(msg: String) = + if (!isShuttingDown) + showResult(colorize(description), msg) + + private def outputPrefix = if (isInsideAnt) "" else markNormal("partest: ") + private def name = src relativize entity.location // e.g. "neg/test.scala" + private def showResult(status: String, extraMsg: String) = + normal(outputPrefix + "[...]/%-40s [%s] %s\n".format(name, status, extraMsg)) + + override def equals(other: Any) = other match { + case x: TestResult => entity == x.entity + case _ => false + } + override def hashCode = entity.hashCode + override def toString = "%s (%s)".format(entity, if (passed) "passed" else "failed") + } + + class Success(val entity: TestEntity) extends TestResult(0, " OK ") { + def colorize(s: String) = markSuccess(s) + override def show(msg: String) = if (!isTerse) super.show(msg) + } + class Failure(val entity: TestEntity) extends TestResult(1, " FAILED ") { + def colorize(s: String) = markFailure(s) + + override def show(msg: String) = { + super.show(msg) + + if (isShowDiff || isTrace) + normal(entity.diffOutput()) + + if (isShowLog || isTrace) + normal(toStringTrunc(entity.failureMessage(), 1600)) + } + } + class Timeout(val entity: TestEntity) extends TestResult(2, "TIME OUT") { + def colorize(s: String) = markFailure(s) + } + + object TestResult { + def apply(entity: TestEntity, success: Boolean) = + if (success) new Success(entity) + else new Failure(entity) + + def apply(entity: TestEntity, state: Int) = state match { + case 0 => new Success(entity) + case 1 => new Failure(entity) + case 2 => new Timeout(entity) + } + def unapply(x: Any) = x match { + case x: TestResult => Some((x.entity, x.state)) + case _ => None + } + } + + /** The combined results of any number of tests. + */ + case class CombinedTestResults( + passed: Int, + failed: Int, + elapsedMilliseconds: Long + ) { + // housekeeping + val elapsedSecs = elapsedMilliseconds / 1000 + val elapsedMins = elapsedSecs / 60 + val elapsedHrs = elapsedMins / 60 + val dispMins = elapsedMins - elapsedHrs * 60 + val dispSecs = elapsedSecs - elapsedMins * 60 + + def total = passed + failed + def hasFailures = failed > 0 + def exitCode = if (expectedErrors == failed) 0 else 1 + + def ++(x: CombinedTestResults) = CombinedTestResults( + passed + x.passed, + failed + x.failed, + elapsedMilliseconds + x.elapsedMilliseconds + ) + + def elapsedString = "%02d:%02d:%02d".format(elapsedHrs, dispMins, dispSecs) + override def toString = + if (total == 0) "There were no tests to run." + else if (isDryRun) "%d tests would be run." format total + else if (hasFailures) "%d of %d tests failed (elapsed time: %s)".format(failed, total, elapsedString) + else "All %d tests were successful (elapsed time: %s)".format(total, elapsedString) + } +}
\ No newline at end of file diff --git a/src/partest/scala/tools/partest/Runner.scala b/src/partest/scala/tools/partest/Runner.scala new file mode 100644 index 0000000000..7f67c93478 --- /dev/null +++ b/src/partest/scala/tools/partest/Runner.scala @@ -0,0 +1,39 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Philipp Haller + */ + +package scala.tools +package partest + +import nsc.io._ + +object Runner { + def main(mainArgs: Array[String]) { + val propArgs = PartestSpecReference.sysPropsAsOptions() + val args = (propArgs ++ mainArgs).toList + val runner = Partest(args: _*) + import runner._ + + if (isVersion) return println(versionMsg) + if (args.isEmpty) return println(helpMsg) + if (isValidate) return validateAll() + + printConfigBanner() + + if (isCleanup) + cleanupAll() + + val result = launchTestSuite() + val exitCode = result.exitCode + val message = "\n" + result + "\n" + + if (exitCode == 0) success(message) + else failure(message) + + if (isStats) + showTestStatistics() + + System exit exitCode + } +} diff --git a/src/partest/scala/tools/partest/Statistics.scala b/src/partest/scala/tools/partest/Statistics.scala new file mode 100644 index 0000000000..2ea3c6e8f0 --- /dev/null +++ b/src/partest/scala/tools/partest/Statistics.scala @@ -0,0 +1,46 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Philipp Haller + */ + +package scala.tools +package partest + +import scala.collection.mutable.HashMap + +trait Statistics { + /** Only collected when --stats is given. */ + lazy val testStatistics = new HashMap[String, Long] + + /** Given function and block of code, evaluates code block, + * calls function with milliseconds elapsed, and returns block result. + */ + def timed[T](f: Long => Unit)(body: => T): T = { + val start = System.currentTimeMillis + val result = body + val end = System.currentTimeMillis + + f(end - start) + result + } + /** Times body and returns both values. + */ + def timed2[T](body: => T): (Long, T) = { + var milliSeconds = 0L + val result = timed(x => milliSeconds = x)(body) + + (milliSeconds, result) + } + + def resultsToStatistics(results: Iterable[(_, Int)]): (Int, Int) = + (results partition (_._2 == 0)) match { + case (winners, losers) => (winners.size, losers.size) + } + + def recordTestTiming(name: String, milliseconds: Long) = + synchronized { testStatistics(name) = milliseconds } + + def showTestStatistics() { + testStatistics.toList sortBy (-_._2) foreach { case (k, v) => println("%s: %.2f seconds".format(k, (v.toDouble / 1000))) } + } +} diff --git a/src/partest/scala/tools/partest/Universe.scala b/src/partest/scala/tools/partest/Universe.scala new file mode 100644 index 0000000000..557d48fe54 --- /dev/null +++ b/src/partest/scala/tools/partest/Universe.scala @@ -0,0 +1,101 @@ +/* __ *\ +** ________ ___ / / ___ Scala Parallel Testing ** +** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.tools +package partest + +import nsc.io._ +import category.AllCategories +import io.Logging + +/** The high level view of the partest infrastructure. + */ +abstract class Universe + extends Entities + with BuildContributors + with Logging + with Dispatcher + with Statistics + with Housekeeping + with Results + with PartestCompilation + with PartestSpec + with Config + with Alarms + with Actions + with Categories { + + /** The abstract values from which all else is derived. */ + def partestDir: Directory + def testBuildDir: Directory + def allCategories: List[TestCategory] + def selectedCategories: List[TestCategory] + + /** Some plausibly abstract types. */ + type TestBuild <: BuildContributor // e.g. quick, pack + type TestCategory <: AbsTestCategory // e.g. pos, neg, run + type TestEntity <: AbsTestEntity // e.g. files/pos/test25.scala + type TestSequence <: AbsTestSequence // e.g. compile, run, diff + + /** Although TestStep isn't much more than Function1 right now, + * it exists this way so it can become more capable. + */ + implicit def f1ToTestStep(f: TestEntity => Boolean): TestStep = + new TestStep { def apply(test: TestEntity) = f(test) } + + abstract class TestStep extends (TestEntity => Boolean) { + def apply(test: TestEntity): Boolean + } + + /** An umbrella category of tests, such as "pos" or "run". + */ + trait AbsTestCategory extends BuildContributor { + type TestSettings + + def kind: String + def testSequence: TestSequence + def denotesTest(location: Path): Boolean + + def createTest(location: Path): TestEntity + def createSettings(entity: TestEntity): TestSettings + def enumerate: List[TestEntity] + } + + /** A single test. It may involve multiple files, but only a + * single path is used to designate it. + */ + trait AbsTestEntity extends BuildContributor { + def category: TestCategory + def location: Path + def onException(x: Throwable): Unit + def testClasspath: String + + /** Any preconditions before running the test. Test fails + * immediately if this returns false. + */ + def precondition: Boolean = true + + /** Most tests will use the sequence defined by the category, + * but the test can override and define a custom sequence. + */ + def testSequence: TestSequence + + /** True if this test recognizes the given path as a piece of it. + * For validation purposes. + */ + def acknowledges(path: Path): Boolean + } + + /** Every TestEntity is partly characterized by a series of actions + * which are applied to the TestEntity in the given order. The test + * passes if all those actions return true, fails otherwise. + */ + trait AbsTestSequence { + def actions: List[TestStep] + } +}
\ No newline at end of file diff --git a/src/partest/scala/tools/partest/ant/JavaTask.scala b/src/partest/scala/tools/partest/ant/JavaTask.scala new file mode 100644 index 0000000000..0bebf91368 --- /dev/null +++ b/src/partest/scala/tools/partest/ant/JavaTask.scala @@ -0,0 +1,55 @@ +/* __ *\ +** ________ ___ / / ___ Scala Parallel Testing ** +** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.tools +package partest +package ant + +import org.apache.tools.ant.Task +import org.apache.tools.ant.taskdefs.Java +import org.apache.tools.ant.types.{ EnumeratedAttribute, Commandline, Environment, PropertySet } + +import scala.tools.nsc.io._ +import scala.tools.nsc.util.{ ClassPath, CommandLineSpec } +import CommandLineSpec._ + +class JavaTask extends Java { + override def getTaskName() = "partest" + private val scalaRunnerClass = "scala.tools.nsc.MainGenericRunner" + + protected def rootDir = prop("partest.rootdir") getOrElse (baseDir / "test").path + protected def partestJVMArgs = prop("partest.jvm.args") getOrElse "-Xms64M -Xmx768M -Xss768K -XX:MaxPermSize=96M" + protected def runnerArgs = List("-usejavacp", "scala.tools.partest.Runner", "--javaopts", partestJVMArgs) + + private def baseDir = Directory(getProject.getBaseDir) + private def prop(s: String) = Option(getProject getProperty s) + private def jvmline(s: String) = returning(createJvmarg())(_ setLine s) + private def addArg(s: String) = returning(createArg())(_ setValue s) + + private def newKeyValue(key: String, value: String) = + returning(new Environment.Variable)(x => { x setKey key ; x setValue value }) + + def setDefaults() { + setFork(true) + setFailonerror(true) + getProject.setSystemProperties() + setClassname(scalaRunnerClass) + addSysproperty(newKeyValue("partest.is-in-ant", "true")) + jvmline(partestJVMArgs) + runnerArgs foreach addArg + + // do we want basedir or rootDir to be the cwd? + // setDir(Path(rootDir).jfile) + } + + override def init() = { + super.init() + setDefaults() + } +} + diff --git a/src/partest/scala/tools/partest/ant/PartestTask.scala b/src/partest/scala/tools/partest/ant/PartestTask.scala new file mode 100644 index 0000000000..65848fabb0 --- /dev/null +++ b/src/partest/scala/tools/partest/ant/PartestTask.scala @@ -0,0 +1,90 @@ +/* __ *\ +** ________ ___ / / ___ Scala Parallel Testing ** +** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +/**** Note -- this isn't used anymore, but I left it in for the moment. ****/ + +package scala.tools +package partest +package ant + +import java.io.{ File => JFile } + +import org.apache.tools.ant.Task +import org.apache.tools.ant.types.{ Reference, FileSet} + +import scala.reflect.BeanProperty +import scala.tools.ant.sabbus.CompilationPathProperty +import scala.tools.nsc.io +import scala.tools.nsc.util.CommandLineSpec._ + +class PartestTask extends Task with CompilationPathProperty { + /** Used only in ant task */ + @BeanProperty protected var errorOnFailed: Boolean = _ + @BeanProperty protected var jUnitReportDir: JFile = _ + + /** Propagated to partest run via system properties */ + @BeanProperty protected var debug: Boolean = _ + @BeanProperty protected var javaOpts: String = _ + @BeanProperty protected var partestOpts: String = _ + @BeanProperty protected var runSets: String = _ + @BeanProperty protected var scalacOpts: String = _ + @BeanProperty protected var showDiff: Boolean = _ + @BeanProperty protected var showLog: Boolean = _ + @BeanProperty protected var srcDir: String = _ + @BeanProperty protected var timeout: Int = _ + + /** Translating ant information into command line arguments. */ + private def notEmpty(s: String) = s != null && s.length > 0 + private def quoted(s: String) = if (s exists (_.isWhitespace)) "\"" + s.trim + "\"" else s + private def optionCollection = List[(Boolean, () => List[String])]( + debug -> (() => List("--debug")), + showLog -> (() => List("--show-log")), + showDiff -> (() => List("--show-diff")), + (timeout > 0) -> (() => List("--timeout", timeout.toString)), + notEmpty(javaOpts) -> (() => List("--javaopts", javaOpts)), + notEmpty(scalacOpts) -> (() => List("--scalacopts", scalacOpts)), + notEmpty(srcDir) -> (() => List("--srcdir", srcDir)), + notEmpty(partestOpts) -> (() => toArgs(partestOpts)) + ) + + private def antPropOrNone(name: String) = Option(getProject getProperty name) + private def antPropsToCommandLine() = { + setProp("partest.isInAnt", "true") + val partestDir = antPropOrNone("partest.dir") getOrElse error("Mandatory attribute 'partest.dir' is not set.") + + val root = List("--rootdir", io.Path(partestDir).path) + val opts = optionCollection collect { case (true, f) => f() } flatten + val sets = Option(runSets).toList flatMap toArgs map toOpt + + root ++ opts ++ sets + } + private def antRunTests() = { + val args = antPropsToCommandLine() + val runner = Partest(args: _*) + import runner._ + + normal("Ant options translate to command line: partest " + fromArgs(args)) + printConfigBanner() + + val result = launchTestSuite() + val msg = result.toString + + if (result.hasFailures && errorOnFailed) error(msg) + else log(msg) + } + + override def execute() { + try antRunTests() + catch { + case x => + System.err.println("Uncaught exception %s in partest ant ask: aborting." format x) + x.printStackTrace() + throw x + } + } +} diff --git a/src/partest/scala/tools/partest/antlib.xml b/src/partest/scala/tools/partest/antlib.xml index b3b98e853f..af36f11368 100644 --- a/src/partest/scala/tools/partest/antlib.xml +++ b/src/partest/scala/tools/partest/antlib.xml @@ -1,4 +1,3 @@ <antlib> - <taskdef name="partest" - classname="scala.tools.partest.PartestTask"/> + <taskdef name="partest" classname="scala.tools.partest.ant.JavaTask"/> </antlib> diff --git a/src/partest/scala/tools/partest/category/AllCategories.scala b/src/partest/scala/tools/partest/category/AllCategories.scala new file mode 100644 index 0000000000..ce6573123a --- /dev/null +++ b/src/partest/scala/tools/partest/category/AllCategories.scala @@ -0,0 +1,20 @@ +/* __ *\ +** ________ ___ / / ___ Scala Parallel Testing ** +** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.tools +package partest +package category + +trait AllCategories extends Compiler with Analysis with Runner { + self: Universe => + + object Pos extends DirBasedCategory("pos") { lazy val testSequence: TestSequence = List(compile) } + object Neg extends DirBasedCategory("neg") { lazy val testSequence: TestSequence = List(not(compile), diff) } + object Run extends DirBasedCategory("run") { lazy val testSequence: TestSequence = List(compile, run, diff) } + object Jvm extends DirBasedCategory("jvm") { lazy val testSequence: TestSequence = List(compile, run, diff) } +} diff --git a/src/partest/scala/tools/partest/category/Analysis.scala b/src/partest/scala/tools/partest/category/Analysis.scala new file mode 100644 index 0000000000..f2b43ebf6d --- /dev/null +++ b/src/partest/scala/tools/partest/category/Analysis.scala @@ -0,0 +1,65 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + */ + +package scala.tools +package partest +package category + +import java.lang.{ ClassLoader => JavaClassLoader } +import java.net.URL +import nsc.util.ScalaClassLoader +import nsc.io._ + +class PartestClassLoader(urls: Array[URL], parent: JavaClassLoader) extends ScalaClassLoader.URLClassLoader(urls, parent) { + def this(urls: Array[URL]) = this(urls, null) + def bytes(path: String) = findBytesForClassName(path) + def singleton(path: String) = tryToInitializeClass(path).get getField "MODULE$" get null + + /** Calls a method in an object via reflection. + */ + def apply[T](className: String, methodName: String)(args: Any*): T = { + def fail = error("Reflection failed on %s.%s".format(className, methodName)) + val clazz = tryToLoadClass(className) getOrElse fail + val obj = singleton(className) + val m = clazz.getMethods find (x => x.getName == methodName && x.getParameterTypes.size == args.size) getOrElse fail + + m.invoke(obj, args map (_.asInstanceOf[AnyRef]): _*).asInstanceOf[T] + } +} + +trait Analysis { + self: Universe => + + object Scalap extends DirBasedCategory("scalap") { + val testSequence: TestSequence = List(compile, run, diff) + override def denotesTest(p: Path) = p.isDirectory && (p.toDirectory.files exists (_.name == "result.test")) + override def createTest(location: Path) = new ScalapTest(location) + + class ScalapTest(val location: Path) extends TestEntity { + val category = Scalap + val scalapMain = "scala.tools.scalap.Main$" + val scalapMethod = "decompileScala" + + override def classpathPaths = super.classpathPaths :+ build.scalap + override def checkFile = File(location / "result.test") + override def precondition = checkFile.isFile && super.precondition + + private def runnerURLs = build.classpathPaths ::: classpathPaths map (_.toURL) + private def createClassLoader = new PartestClassLoader(runnerURLs.toArray, this.getClass.getClassLoader) + + val isPackageObject = containsString("package object") + val suffix = if (isPackageObject) ".package" else "" + val className = location.name.capitalize + suffix + + override def run() = loggingResult { + def loader = createClassLoader + def bytes = loader.bytes(className) + + trace("scalap %s".format(className)) + if (isDryRun) "" + else loader[String](scalapMain, scalapMethod)(bytes, isPackageObject) + } + } + } +} diff --git a/src/partest/scala/tools/partest/category/Compiler.scala b/src/partest/scala/tools/partest/category/Compiler.scala new file mode 100644 index 0000000000..58fd8230e2 --- /dev/null +++ b/src/partest/scala/tools/partest/category/Compiler.scala @@ -0,0 +1,142 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + */ + +package scala.tools +package partest +package category + +import nsc.io._ +import nsc.reporters._ +import nsc.{ Settings, CompilerCommand } +import scala.tools.nsc.interactive.RefinedBuildManager +import util.copyPath + +trait Compiler { + self: Universe => + + /** Resident Compiler. + * $SCALAC -d dir.obj -Xresident -sourcepath . "$@" + */ + object Res extends DirBasedCategory("res") { + lazy val testSequence: TestSequence = List(compile, diff) + + override def denotesTest(p: Path) = p.isDirectory && resFile(p).isFile + override def createTest(location: Path) = new ResidentTest(location.toDirectory) + + override def createSettings(entity: TestEntity): TestSettings = + returning(super.createSettings(entity)) { settings => + settings.resident.value = true + settings.sourcepath.value = entity.sourcesDir.path + } + + class ResidentTest(val location: Directory) extends TestEntity { + val category = Res + override def precondition = checkFile.isFile && super.precondition + override def sourcesDir = categoryDir + + override def acknowledges(p: Path) = + super.acknowledges(p) || (resFile(location) isSame p) + + private def residentCompilerCommands = safeLines(resFile(location)) + private def compileResident(global: PartestGlobal, lines: List[String]) = { + def printPrompt = global inform "nsc> " + val results = + lines map { line => + printPrompt + trace("compile " + line) + isDryRun || global.partestCompile(toArgs(line) map (categoryDir / _ path), false) + } + + printPrompt + + /** Note - some res tests are really "neg" style tests, so we can't + * use the return value of the compile. The diff catches failures. + */ + true // results forall (_ == true) + } + + override def compile() = compileResident(newGlobal(Nil)._1, residentCompilerCommands) + } + private[Res] def resFile(p: Path) = p.toFile addExtension "res" + } + + object BuildManager extends DirBasedCategory("buildmanager") { + lazy val testSequence: TestSequence = List(compile, diff) + override def denotesTest(p: Path) = p.isDirectory && testFile(p).isFile + override def createTest(location: Path) = new BuildManagerTest(location.toDirectory) + + override def createSettings(entity: TestEntity): TestSettings = + returning[TestSettings](super.createSettings(entity)) { settings => + settings.Ybuildmanagerdebug.value = true + settings.sourcepath.value = entity.sourcesDir.path + } + + class PartestBuildManager(settings: Settings, val reporter: ConsoleReporter) extends RefinedBuildManager(settings) { + def errorFn(msg: String) = Console println msg + + override protected def newCompiler(newSettings: Settings) = + new BuilderGlobal(newSettings, reporter) + + private def filesToSet(pre: String, fs: List[String]): Set[AbstractFile] = + fs flatMap (s => Option(AbstractFile getFile (Path(settings.sourcepath.value) / s path))) toSet + + def buildManagerCompile(line: String): Boolean = { + val prompt = "builder > " + reporter printMessage (prompt + line) + val command = new CompilerCommand(toArgs(line), settings) + val files = filesToSet(settings.sourcepath.value, command.files) + + update(files, Set.empty) + true + } + } + + private[BuildManager] def testFile(p: Path) = (p / p.name addExtension "test").toFile + + class BuildManagerTest(val location: Directory) extends TestEntity { + val category = BuildManager + + override def sourcesDir = outDir + override def sourceFiles = Path onlyFiles (location walkFilter (_ != changesDir) filter isJavaOrScala toList) + override def checkFile = File(location / location.name addExtension "check") + override def precondition = checkFile.isFile && super.precondition + + override def acknowledges(p: Path) = super.acknowledges(p) || (p isSame testFile(location)) + + def buildManagerCommands = safeLines(testFile(location)) + def changesDir = Directory(location / (location.name + ".changes")) + + override def compile() = { + val settings = createSettings(this) + val pbm = new PartestBuildManager(settings, newReporter(settings)) + + // copy files + for (source <- sourceFiles) { + val target = outDir / (location.normalize relativize source) + copyPath(source, target.toFile) + } + + def runUpdate(line: String) = { + val Array(srcName, replacement) = line split "=>" + copyPath(File(changesDir / replacement), File(outDir / srcName)) + } + + def sendCommand(line: String): Boolean = { + val compileRegex = """^>>compile (.*)$""".r + val updateRegex = """^>>update\s+(.*)""".r + trace(line drop 2) + + isDryRun || (line match { + case compileRegex(xs) => pbm.buildManagerCompile(xs) + case updateRegex(line) => runUpdate(line) + }) + } + + // send each line to the build manager + buildManagerCommands forall sendCommand + } + } + } +} + diff --git a/src/partest/scala/tools/partest/category/Runner.scala b/src/partest/scala/tools/partest/category/Runner.scala new file mode 100644 index 0000000000..a7713d7dbe --- /dev/null +++ b/src/partest/scala/tools/partest/category/Runner.scala @@ -0,0 +1,108 @@ +/* __ *\ +** ________ ___ / / ___ Scala Parallel Testing ** +** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.tools +package partest +package category + +import nsc.io._ + +trait Runner { + self: Universe => + + /** Shootout. + */ + object Shootout extends DirBasedCategory("shootout") { + lazy val testSequence: TestSequence = List(compile, run, diff) + + override def denotesTest(p: Path) = isScala(p) && runner(p).isFile + override def createTest(location: Path) = new ShootoutTest(location.toFile) + + class ShootoutTest(val location: File) extends TestEntity { + val category = Shootout + // The files in shootout are very free form, so acknowledge anything close. + override def acknowledges(p: Path) = + (p.parent.normalize isSame Shootout.root) && (p.name startsWith label) + + private def generated = File(outDir / "test.scala") + private def runnerFile = runner(location) + override def sourceFiles = List(generated) + + override def compile() = { + trace("generate %s from %s, %s".format(tracePath(generated), tracePath(location), tracePath(runnerFile))) + // generate source file (even on dry run, we need the path) + generated.writeAll(location.slurp(), runnerFile.slurp()) + + // compile generated file + super.compile() + } + } + + private[Shootout] def runner(p: Path) = p addExtension "runner" toFile + } + + object Scalacheck extends DirBasedCategory("scalacheck") { + lazy val testSequence: TestSequence = List(compile, run) + override def createTest(location: Path) = new ScalacheckTest(location) + + class ScalacheckTest(val location: Path) extends TestEntity { + val category = Scalacheck + + import build.{ scalacheck, forkjoin } + import org.scalacheck.Properties + import org.scalacheck.Test.{ checkProperties, defaultParams, Result } + + override def classpathPaths = super.classpathPaths ::: List(scalacheck, forkjoin) + private def arrayURLs = Array(scalacheck, outDir) map (_.toURL) + + /** For reasons I'm not entirely clear on, I've written all this + * to avoid a source dependency on scalacheck. + */ + class ScalacheckClassLoader extends PartestClassLoader(arrayURLs, this.getClass.getClassLoader) { + type ScalacheckResult = { def passed: Boolean } + + def propCallback(name: String, passed: Int, discarded: Int): Unit = () + def testCallback(name: String, result: AnyRef): Unit = () + + val test = singleton("Test$") + val params = apply[AnyRef]("org.scalacheck.Test$", "defaultParams")() + val result = apply[Seq[(String, AnyRef)]]("org.scalacheck.Test$", "checkProperties")(test, params, propCallback _, testCallback _) + + def allResults() = + for ((prop, res) <- result) yield { + ScalacheckTest.this.trace("scalacheck result for %s: %s".format(prop, res)) + res.asInstanceOf[ScalacheckResult].passed + } + + def check() = allResults forall (_ == true) + } + + override def run() = { + trace("scalacheck runs via classloader with: %s".format(arrayURLs mkString ", ")) + isDryRun || (new ScalacheckClassLoader check) + } + } + } + + object Script extends DirBasedCategory("script") { + val testSequence: TestSequence = List(exec, diff) + override def createTest(location: Path) = new ScriptTest(location) + + class ScriptTest(val location: Path) extends TestEntity { + val category = Script + val scriptFile = if (location.isDirectory) location / (label + ".scala") else location + val argsFile = withExtension("args").toFile + def batFile = scriptFile changeExtension "bat" + def script = if (Properties.isWin) batFile else scriptFile + + override def acknowledges(p: Path) = super.acknowledges(p) || (List(argsFile, batFile) exists (_ isSame p)) + override def execCwd = Some(sourcesDir) + override def argumentsToExec = script.path :: safeArgs(argsFile) + } + } +}
\ No newline at end of file diff --git a/src/partest/scala/tools/partest/io/ANSIWriter.scala b/src/partest/scala/tools/partest/io/ANSIWriter.scala new file mode 100644 index 0000000000..0ddcd97a5f --- /dev/null +++ b/src/partest/scala/tools/partest/io/ANSIWriter.scala @@ -0,0 +1,58 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Philipp Haller + */ + +package scala.tools +package partest +package io + +import java.io.{ Writer, PrintWriter, OutputStream, OutputStreamWriter } + +object ANSIWriter { + val NONE = 0 + val SOME = 1 + val MANY = 2 + + def apply(isAnsi: Boolean) = if (isAnsi) MANY else NONE +} +import ANSIWriter._ + +class ANSIWriter(writer: Writer) extends PrintWriter(writer, true) { + def this(out: OutputStream) = this(new OutputStreamWriter(out)) + def colorful: Int = NONE + + protected val manyColors = List( + Console.BOLD + Console.BLACK, + Console.BOLD + Console.GREEN, + Console.BOLD + Console.RED, + Console.BOLD + Console.YELLOW, + Console.RESET + ) + protected val someColors = List( + Console.BOLD + Console.BLACK, + Console.RESET, + Console.BOLD + Console.BLACK, + Console.BOLD + Console.BLACK, + Console.RESET + ) + protected val noColors = List("", "", "", "", "") + + lazy val List(_outline, _success, _failure, _warning, _default) = colorful match { + case NONE => noColors + case SOME => someColors + case MANY => manyColors + case _ => noColors + } + + private def wrprint(msg: String): Unit = synchronized { + print(msg) + flush() + } + + def outline(msg: String) = wrprint(_outline + msg + _default) + def success(msg: String) = wrprint(_success + msg + _default) + def failure(msg: String) = wrprint(_failure + msg + _default) + def warning(msg: String) = wrprint(_warning + msg + _default) + def normal(msg: String) = wrprint(_default + msg) +} diff --git a/src/partest/scala/tools/partest/nest/Diff.java b/src/partest/scala/tools/partest/io/Diff.java index abd09d0293..c7a3d42f30 100644 --- a/src/partest/scala/tools/partest/nest/Diff.java +++ b/src/partest/scala/tools/partest/io/Diff.java @@ -1,6 +1,6 @@ // $Id$ -package scala.tools.partest.nest; +package scala.tools.partest.io; import java.util.Hashtable; diff --git a/src/partest/scala/tools/partest/nest/DiffPrint.java b/src/partest/scala/tools/partest/io/DiffPrint.java index 494bc06e4a..2b2ad93ec7 100644 --- a/src/partest/scala/tools/partest/nest/DiffPrint.java +++ b/src/partest/scala/tools/partest/io/DiffPrint.java @@ -1,6 +1,6 @@ // $Id$ -package scala.tools.partest.nest; +package scala.tools.partest.io; import java.io.*; import java.util.Vector; diff --git a/src/partest/scala/tools/partest/io/JUnitReport.scala b/src/partest/scala/tools/partest/io/JUnitReport.scala new file mode 100644 index 0000000000..63ae200020 --- /dev/null +++ b/src/partest/scala/tools/partest/io/JUnitReport.scala @@ -0,0 +1,38 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + */ + +package scala.tools +package partest +package io + +/** This is disabled for the moment but I can fix it up if anyone + * is using it. + */ +class JUnitReport { + // create JUnit Report xml files if directory was specified + // def junitReport(dir: Directory) = { + // dir.mkdir() + // val report = testReport(set.kind, results, succs, fails) + // XML.save("%s/%s.xml".format(d.toAbsolute.path, set.kind), report) + // } + + // def oneResult(res: (TestEntity, Int)) = + // <testcase name={res._1.path}>{ + // res._2 match { + // case 0 => scala.xml.NodeSeq.Empty + // case 1 => <failure message="Test failed"/> + // case 2 => <failure message="Test timed out"/> + // } + // }</testcase> + // + // def testReport(kind: String, results: Iterable[(TestEntity, Int)], succs: Int, fails: Int) = { + // <testsuite name={kind} tests={(succs + fails).toString} failures={fails.toString}> + // <properties/> + // { + // results.map(oneResult(_)) + // } + // </testsuite> + // } + // +}
\ No newline at end of file diff --git a/src/partest/scala/tools/partest/io/Logging.scala b/src/partest/scala/tools/partest/io/Logging.scala new file mode 100644 index 0000000000..3d1b0fa0b4 --- /dev/null +++ b/src/partest/scala/tools/partest/io/Logging.scala @@ -0,0 +1,132 @@ +package scala.tools +package partest +package io + +import java.io.{ StringWriter, PrintWriter, Writer } +import scala.tools.nsc.io._ +import scala.util.control.ControlThrowable + +trait Logging { + universe: Universe => + + class PartestANSIWriter extends ANSIWriter(Console.out) { + override def colorful: Int = ANSIWriter(universe.isAnsi) + private def printIf(cond: Boolean, msg: String) = + if (cond) { outline("debug: ") ; println(msg) } + + val verbose = printIf(isVerbose || isDebug, _: String) + val debug = printIf(isDebug, _: String) + } + + lazy val NestUI = new PartestANSIWriter() + + import NestUI.{ _outline, _success, _failure, _warning, _default } + + def markOutline(msg: String) = _outline + msg + _default + def markSuccess(msg: String) = _success + msg + _default + def markFailure(msg: String) = _failure + msg + _default + def markWarning(msg: String) = _warning + msg + _default + def markNormal(msg: String) = _default + msg + + def outline(msg: String) = NestUI outline msg + def success(msg: String) = NestUI success msg + def failure(msg: String) = NestUI failure msg + def warning(msg: String) = NestUI warning msg + def normal(msg: String) = NestUI normal msg + + def verbose(msg: String) = NestUI verbose msg + def debug(msg: String) = NestUI debug msg + + trait EntityLogging { + self: TestEntity => + + lazy val logWriter = new LogWriter(logFile) + + /** Redirect stdout and stderr to logFile, run body, return result. + */ + def loggingOutAndErr[T](body: => T): T = { + val log = logFile.printStream(append = true) + + try Console.withOut(log) { + Console.withErr(log) { + body + } + } + finally log.close() + } + + /** XXX needs attention. + */ + def failureMessage() = safeSlurp(logFile) + + /** For tracing. Outputs a line describing the next action. tracePath + * is a path wrapper which prints name or full path depending on verbosity. + */ + def trace(msg: String) = if (isTrace || isDryRun) System.err.println(">> [%s] %s".format(label, msg)) + def tracePath(path: Path) = if (isVerbose) path.path else path.name + + /** v == verbose. + */ + def vtrace(msg: String) = if (isVerbose) trace(msg) + + /** Run body, writes result to logFile. Any throwable is + * caught, stringified, and written to the log. + */ + def loggingResult(body: => String) = + try returning(true)(_ => logFile writeAll body) + catch { + case x: ControlThrowable => throw x + case x: Throwable => logException(x) + } + + def throwableToString(x: Throwable): String = { + val w = new StringWriter + x.printStackTrace(new PrintWriter(w)) + w.toString + } + + def warnAndLogException(msg: String, ex: Throwable) = { + val str = msg + throwableToString(ex) + warning(toStringTrunc(str, 800)) + logWriter append str + } + + def deleteLog(force: Boolean = false) = + if (universe.isNoCleanup && !force) debug("Not cleaning up " + logFile) + else logFile.deleteIfExists() + + def onException(x: Throwable) { logException(x) } + def logException(x: Throwable) = { + val msg = throwableToString(x) + if (!isTerse) + normal(msg) + + logWriter append msg + false + } + } + + /** A writer which doesn't create the file until a write comes in. + */ + class LazilyCreatedWriter(log: File) extends Writer { + @volatile private var isCreated = false + private lazy val underlying = { + isCreated = true + log.bufferedWriter() + } + + def flush() = if (isCreated) underlying.flush() + def close() = if (isCreated) underlying.close() + def write(chars: Array[Char], off: Int, len: Int) = { + underlying.write(chars, off, len) + underlying.flush() + } + } + + class LogWriter(log: File) extends PrintWriter(new LazilyCreatedWriter(log), true) { + override def print(s: String) = { + super.print(s) + flush() + } + } +}
\ No newline at end of file diff --git a/src/partest/scala/tools/partest/nest/AntRunner.scala b/src/partest/scala/tools/partest/nest/AntRunner.scala deleted file mode 100644 index cdc6961d3d..0000000000 --- a/src/partest/scala/tools/partest/nest/AntRunner.scala +++ /dev/null @@ -1,32 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala Parallel Testing ** -** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -// $Id$ - -package scala.tools.partest -package nest - -import java.io.File - -class AntRunner extends DirectRunner { - - val fileManager = new FileManager { - var JAVACMD: String = "java" - var JAVAC_CMD: String = "javac" - var CLASSPATH: String = _ - var EXT_CLASSPATH: String = _ - var LATEST_LIB: String = _ - val TESTROOT: String = "" - } - - def reflectiveRunTestsForFiles(kindFiles: Array[File], kind: String): Int = { - val (succs, fails) = runTestsForFiles(kindFiles.toList, kind) - succs << 16 | fails - } - -} 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 b67653d900..0000000000 --- a/src/partest/scala/tools/partest/nest/CompileManager.scala +++ /dev/null @@ -1,218 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -import scala.tools.nsc.{Global, Settings, CompilerCommand, FatalError} -import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} - -import java.io.{File, BufferedReader, PrintWriter, FileReader, FileWriter, StringWriter} - -class ExtConsoleReporter(override val settings: Settings, reader: BufferedReader, var writer: PrintWriter) extends ConsoleReporter(settings, reader, writer) { - def this(settings: Settings) = { - this(settings, Console.in, new PrintWriter(new FileWriter("/dev/null"))) - } - def hasWarnings: Boolean = WARNING.count != 0 -} - -abstract class SimpleCompiler { - def compile(out: Option[File], files: List[File], kind: String, log: File): Boolean -} - -class TestSettings(fileMan: FileManager) extends { - override val bootclasspathDefault = - System.getProperty("sun.boot.class.path", "") + File.pathSeparator + - fileMan.LATEST_LIB - override val extdirsDefault = - System.getProperty("java.ext.dirs", "") -} with Settings(x => ()) - -class DirectCompiler(val fileManager: FileManager) extends SimpleCompiler { - def newGlobal(settings: Settings, reporter: Reporter): Global = - new Global(settings, reporter) - - def newGlobal(settings: Settings, logWriter: FileWriter): Global = { - val rep = new ExtConsoleReporter(settings, - Console.in, - new PrintWriter(logWriter)) - rep.shortname = true - newGlobal(settings, rep) - } - - def newSettings = { - val settings = new TestSettings(fileManager) - settings.deprecation.value = true - settings.nowarnings.value = false - settings.encoding.value = "iso-8859-1" - settings - } - - def newReporter(sett: Settings) = new ExtConsoleReporter(sett, - Console.in, - new PrintWriter(new StringWriter)) - - private def updatePluginPath(options: String): String = { - val (opt1, opt2) = - (options split "\\s").toList partition (_ startsWith "-Xplugin:") - (opt2 mkString " ")+( - if (opt1.isEmpty) "" - else { - def absolutize(path: String): List[String] = { - val args = (path substring 9 split File.pathSeparator).toList - val plugins = args map (arg => - if (new File(arg).isAbsolute) arg - else fileManager.TESTROOT+File.separator+arg - ) - plugins - } - " -Xplugin:"+((opt1 flatMap absolutize) mkString File.pathSeparator) - } - ) - } - - def compile(out: Option[File], files: List[File], kind: String, log: File): Boolean = { - val testSettings = newSettings - val logWriter = new FileWriter(log) - - // check whether there is a ".flags" file - val testBase = { - val logBase = fileManager.basename(log.getName) - logBase.substring(0, logBase.length-4) - } - val argsFile = new File(log.getParentFile, testBase+".flags") - val argString = if (argsFile.exists) { - val fileReader = new FileReader(argsFile) - val reader = new BufferedReader(fileReader) - val options = updatePluginPath(reader.readLine()) - reader.close() - options - } else "" - val allOpts = fileManager.SCALAC_OPTS+" "+argString - NestUI.verbose("scalac options: "+allOpts) - val args = (allOpts split "\\s").toList - val command = new CompilerCommand(args, testSettings, x => {}, false) - val global = newGlobal(command.settings, logWriter) - val testRep: ExtConsoleReporter = global.reporter.asInstanceOf[ExtConsoleReporter] - - val test: TestFile = kind match { - case "pos" => PosTestFile(files(0), fileManager, out.isEmpty) - case "neg" => NegTestFile(files(0), fileManager, out.isEmpty) - case "run" => RunTestFile(files(0), fileManager, out.isEmpty) - case "jvm" => JvmTestFile(files(0), fileManager, out.isEmpty) - case "shootout" => ShootoutTestFile(files(0), fileManager, out.isEmpty) - case "scalap" => ScalapTestFile(files(0), fileManager, out.isEmpty) - case "scalacheck" => - ScalaCheckTestFile(files(0), fileManager, out.isEmpty) - } - test.defineSettings(command.settings) - out match { - case Some(outDir) => - command.settings.outdir.value = outDir.getAbsolutePath - command.settings.classpath.value = command.settings.classpath.value+ - File.pathSeparator+outDir.getAbsolutePath - case None => - // do nothing - } - - val toCompile = files.map(_.getPath) - try { - NestUI.verbose("compiling "+toCompile) - try { - (new global.Run) compile toCompile - } catch { - case FatalError(msg) => - testRep.error(null, "fatal error: " + msg) - } - testRep.printSummary - testRep.writer.flush - testRep.writer.close - } catch { - case e => - e.printStackTrace() - return false - } finally { - logWriter.close() - } - !testRep.hasErrors - } -} - -class ReflectiveCompiler(val fileManager: ConsoleFileManager) extends SimpleCompiler { - import fileManager.{latestCompFile, latestPartestFile} - - val sepUrls = Array(latestCompFile.toURI.toURL, latestPartestFile.toURI.toURL) - //NestUI.verbose("constructing URLClassLoader from URLs "+latestCompFile+" and "+latestPartestFile) - - val sepLoader = new java.net.URLClassLoader(sepUrls, null) - - val sepCompilerClass = - sepLoader.loadClass("scala.tools.partest.nest.DirectCompiler") - val sepCompiler = sepCompilerClass.newInstance() - - // needed for reflective invocation - val fileClass = Class.forName("java.io.File") - val stringClass = Class.forName("java.lang.String") - val sepCompileMethod = - sepCompilerClass.getMethod("compile", fileClass, stringClass) - val sepCompileMethod2 = - sepCompilerClass.getMethod("compile", fileClass, stringClass, fileClass) - - /* This method throws java.lang.reflect.InvocationTargetException - * if the compiler crashes. - * This exception is handled in the shouldCompile and shouldFailCompile - * methods of class CompileManager. - */ - def compile(out: Option[File], files: List[File], kind: String, log: File): Boolean = { - val res = sepCompileMethod2.invoke(sepCompiler, out, files, kind, log).asInstanceOf[java.lang.Boolean] - res.booleanValue() - } -} - -class CompileManager(val fileManager: FileManager) { - var compiler: SimpleCompiler = new /*ReflectiveCompiler*/ DirectCompiler(fileManager) - - var numSeparateCompilers = 1 - def createSeparateCompiler() = { - numSeparateCompilers += 1 - compiler = new /*ReflectiveCompiler*/ DirectCompiler(fileManager) - } - - /* This method returns true iff compilation succeeds. - */ - def shouldCompile(files: List[File], kind: String, log: File): Boolean = { - createSeparateCompiler() - compiler.compile(None, files, kind, log) - } - - /* This method returns true iff compilation succeeds. - */ - def shouldCompile(out: File, files: List[File], kind: String, log: File): Boolean = { - createSeparateCompiler() - compiler.compile(Some(out), files, kind, log) - } - - /* This method returns true iff compilation fails - * _and_ the compiler does _not_ crash or loop. - * - * If the compiler crashes, this method returns false. - */ - def shouldFailCompile(files: List[File], kind: String, log: File): Boolean = { - createSeparateCompiler() - !compiler.compile(None, files, kind, log) - } - - /* This method returns true iff compilation fails - * _and_ the compiler does _not_ crash or loop. - * - * If the compiler crashes, this method returns false. - */ - def shouldFailCompile(out: File, files: List[File], kind: String, log: File): Boolean = { - createSeparateCompiler() - !compiler.compile(Some(out), files, kind, log) - } -} diff --git a/src/partest/scala/tools/partest/nest/ConsoleFileManager.scala b/src/partest/scala/tools/partest/nest/ConsoleFileManager.scala deleted file mode 100644 index 27cd36e3e7..0000000000 --- a/src/partest/scala/tools/partest/nest/ConsoleFileManager.scala +++ /dev/null @@ -1,314 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -import java.io.{File, FilenameFilter, IOException, StringWriter} -import java.net.URI - -class ConsoleFileManager extends FileManager { - - var testBuild = System.getProperty("scalatest.build") - var testClasses: Option[String] = None - - val debug: Boolean = - (System.getProperty("partest.debug", "false") equals "true") || - (System.getProperty("scalatest.debug", "false") equals "true") - - def this(buildPath: String, rawClasses: Boolean) = { - this() - if (rawClasses) - testClasses = Some(buildPath) - else - testBuild = buildPath - // re-run because initialization of default - // constructor must be updated - findLatest() - } - - def this(buildPath: String) = { - this(buildPath, false) - } - - def this(buildPath: String, rawClasses: Boolean, moreOpts: String) = { - this(buildPath, rawClasses) - SCALAC_OPTS = SCALAC_OPTS+" "+moreOpts - } - - var CLASSPATH = System.getProperty("java.class.path", ".") - NestUI.verbose("CLASSPATH: "+CLASSPATH) - - var JAVACMD = System.getProperty("scalatest.javacmd", "java") - var JAVAC_CMD = System.getProperty("scalatest.javac_cmd", "javac") - - val prefixFile = { - val cwd = System.getProperty("user.dir") - if (cwd != null) - (new File(cwd)).getCanonicalFile - else - error("user.dir property not set") - } - val PREFIX = prefixFile.getAbsolutePath - -/* -if [ -d "$PREFIX/test" ]; then - TESTROOT="$PREFIX/test"; -elif [ -d "$PREFIX/misc/scala-test" ]; then - TESTROOT="$PREFIX/misc/scala-test"; -else - abort "Test directory not found"; -*/ - - val testRootFile = { - val testRootProp = System.getProperty("scalatest.root") - val testroot = - if (testRootProp != null) - new File(testRootProp) - else { - // case 1: cwd is `test` - if (prefixFile.getName == "test" && (new File(prefixFile, "files")).exists) - prefixFile - else { - // case 2: cwd is `test/..` - val test = new File(prefixFile, "test") - val scalaTest = new File(new File(prefixFile, "misc"), "scala-test") - if (test.isDirectory) - test - else if (scalaTest.isDirectory) - scalaTest - else - error("Test directory not found") - } - } - testroot.getCanonicalFile - } - val TESTROOT = testRootFile.getAbsolutePath - - var srcDirName: String = "" - - val srcDir: File = { - val srcDirProp = System.getProperty("partest.srcdir") - val src = - if (srcDirProp != null) { - srcDirName = srcDirProp - new File(testRootFile, srcDirName) - } else { - srcDirName = "files" - new File(testRootFile, srcDirName) - } - if (src.isDirectory) - src.getCanonicalFile - else { - val path = TESTROOT + File.separator + "files" - NestUI.failure("Source directory \"" + path + "\" not found") - exit(1) - } - } - - LIB_DIR = (new File(testRootFile.getParentFile, "lib")).getCanonicalFile.getAbsolutePath - - CLASSPATH = CLASSPATH + File.pathSeparator + { - val libs = new File(srcDir, "lib") - // add all jars in libs - (libs.listFiles(new FilenameFilter { - def accept(dir: File, name: String) = name endsWith ".jar" - }) map {file => file.getCanonicalFile.getAbsolutePath}).mkString(""+File.pathSeparator) - } - - def findLatest() { - val testParent = testRootFile.getParentFile - NestUI.verbose("test parent: "+testParent) - - def prefixFileWith(parent: File, relPath: String): File = - (new File(parent, relPath)).getCanonicalFile - - def prefixFile(relPath: String): File = - prefixFileWith(testParent, relPath) - - if (!testClasses.isEmpty) { - testClassesFile = (new File(testClasses.get)).getCanonicalFile - NestUI.verbose("Running with classes in "+testClassesFile) - latestFile = prefixFileWith(testClassesFile.getParentFile, "bin") - latestLibFile = prefixFileWith(testClassesFile, "library") - latestActFile = prefixFileWith(testClassesFile, "library") - latestCompFile = prefixFileWith(testClassesFile, "compiler") - latestPartestFile = prefixFileWith(testClassesFile, "partest") - latestFjbgFile = prefixFile("lib/fjbg.jar") - } - else if (testBuild != null) { - testBuildFile = prefixFile(testBuild) - NestUI.verbose("Running on "+testBuild) - latestFile = prefixFile(testBuild+"/bin") - latestLibFile = prefixFile(testBuild+"/lib/scala-library.jar") - latestActFile = prefixFile(testBuild+"/lib/scala-library.jar") - latestCompFile = prefixFile(testBuild+"/lib/scala-compiler.jar") - latestPartestFile = prefixFile(testBuild+"/lib/scala-partest.jar") - } else { - def setupQuick() { - NestUI.verbose("Running build/quick") - latestFile = prefixFile("build/quick/bin") - latestLibFile = prefixFile("build/quick/classes/library") - latestActFile = prefixFile("build/quick/classes/library") - latestCompFile = prefixFile("build/quick/classes/compiler") - latestPartestFile = prefixFile("build/quick/classes/partest") - } - - def setupInst() { - NestUI.verbose("Running dist (installed)") - val p = testParent.getParentFile - latestFile = prefixFileWith(p, "bin") - latestLibFile = prefixFileWith(p, "lib/scala-library.jar") - latestActFile = prefixFileWith(p, "lib/scala-library.jar") - latestCompFile = prefixFileWith(p, "lib/scala-compiler.jar") - latestPartestFile = prefixFileWith(p, "lib/scala-partest.jar") - } - - def setupDist() { - NestUI.verbose("Running dists/latest") - latestFile = prefixFile("dists/latest/bin") - latestLibFile = prefixFile("dists/latest/lib/scala-library.jar") - latestActFile = prefixFile("dists/latest/lib/scala-library.jar") - latestCompFile = prefixFile("dists/latest/lib/scala-compiler.jar") - latestPartestFile = prefixFile("dists/latest/lib/scala-partest.jar") - } - - def setupPack() { - NestUI.verbose("Running build/pack") - latestFile = prefixFile("build/pack/bin") - latestLibFile = prefixFile("build/pack/lib/scala-library.jar") - latestActFile = prefixFile("build/pack/lib/scala-library.jar") - latestCompFile = prefixFile("build/pack/lib/scala-compiler.jar") - latestPartestFile = prefixFile("build/pack/lib/scala-partest.jar") - } - - def max(a: Long, b: Long) = if (a > b) a else b - - val dists = new File(testParent, "dists") - val build = new File(testParent, "build") - // in case of an installed dist, testRootFile is one level deeper - val bin = new File(testParent.getParentFile, "bin") - - // detect most recent build - val quickTime = - max(prefixFile("build/quick/classes/compiler/compiler.properties").lastModified, - prefixFile("build/quick/classes/library/library.properties").lastModified) - val packTime = - max(prefixFile("build/pack/lib/scala-compiler.jar").lastModified, - prefixFile("build/pack/lib/scala-library.jar").lastModified) - val distTime = - max(prefixFile("dists/latest/lib/scala-compiler.jar").lastModified, - prefixFile("dists/latest/lib/scala-library.jar").lastModified) - val instTime = { - val p = testParent.getParentFile - max(prefixFileWith(p, "lib/scala-compiler.jar").lastModified, - prefixFileWith(p, "lib/scala-library.jar").lastModified) - } - - if (quickTime > packTime) { // pack ruled out - if (quickTime > distTime) { // dist ruled out - if (quickTime > instTime) // inst ruled out - setupQuick() - else - setupInst() - } else { // quick ruled out - if (distTime > instTime) // inst ruled out - setupDist() - else - setupInst() - } - } else { // quick ruled out - if (packTime > distTime) { // dist ruled out - if (packTime > instTime) // inst ruled out - setupPack() - else - setupInst() - } else { // pack ruled out - if (distTime > instTime) // inst ruled out - setupDist() - else - setupInst() - } - } - latestFjbgFile = prefixFile("lib/fjbg.jar") - } - - BIN_DIR = latestFile.getAbsolutePath - LATEST_LIB = latestLibFile.getAbsolutePath - LATEST_COMP = latestCompFile.getAbsolutePath - LATEST_PARTEST = latestPartestFile.getAbsolutePath - - // detect whether we are running on Windows - val osName = System.getProperty("os.name") - NestUI.verbose("OS: "+osName) - - val scalaCommand = if (osName startsWith "Windows") - "scala.bat" else "scala" - val scalacCommand = if (osName startsWith "Windows") - "scalac.bat" else "scalac" - - SCALA = (new File(latestFile, scalaCommand)).getAbsolutePath - SCALAC_CMD = (new File(latestFile, scalacCommand)).getAbsolutePath - } - - var BIN_DIR: String = "" - var LATEST_LIB: String = "" - var LATEST_COMP: String = "" - var LATEST_PARTEST: String = "" - var SCALA: String = "" - var SCALAC_CMD: String = "" - - var latestFile: File = _ - var latestLibFile: File = _ - var latestActFile: File = _ - var latestCompFile: File = _ - var latestPartestFile: File = _ - var latestFjbgFile: File = _ - var testBuildFile: File = _ - var testClassesFile: File = _ - // initialize above fields - findLatest() - - var testFiles: List[File] = List() - - def getFiles(kind: String, doCheck: Boolean, filter: Option[(String, Boolean)]): List[File] = { - val dir = new File(srcDir, kind) - NestUI.verbose("look in "+dir+" for tests") - val files = if (dir.isDirectory) { - if (!testFiles.isEmpty) { - val dirpath = dir.getAbsolutePath - testFiles filter { _.getParentFile.getAbsolutePath == dirpath } - } else if (doCheck) filter match { - case Some((ending, enableDirs)) => - val filter = new FilenameFilter { - def accept(dir: File, name: String) = - name.endsWith(ending) || - (enableDirs && (name != ".svn") && (!name.endsWith(".obj")) && - (new File(dir, name)).isDirectory) - } - dir.listFiles(filter).toList - case None => - val filter = new FilenameFilter { - def accept(dir: File, name: String) = name != ".svn" - } - dir.listFiles(filter).toList - } else // skip - Nil - } else { - NestUI.failure("Directory \"" + dir.getPath + "\" not found") - Nil - } - if (failed) - files filter { logFileExists(_, kind) } - else - files - } - - def getFiles(kind: String, doCheck: Boolean): List[File] = - getFiles(kind, doCheck, Some((".scala", true))) - -} diff --git a/src/partest/scala/tools/partest/nest/ConsoleRunner.scala b/src/partest/scala/tools/partest/nest/ConsoleRunner.scala deleted file mode 100644 index 574cc70762..0000000000 --- a/src/partest/scala/tools/partest/nest/ConsoleRunner.scala +++ /dev/null @@ -1,237 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -import java.io.{File, PrintStream, FileOutputStream, BufferedReader, - InputStreamReader, StringWriter, PrintWriter} -import utils.Properties._ - -class ConsoleRunner extends DirectRunner with RunnerUtils { - - case class TestSet(loc: String, - filter: Option[(String, Boolean)], - kind: String, - msg: String) - - val testSets = { - val fileFilter = Some((".scala", true)) - List( - TestSet("pos", fileFilter, "pos", - "Testing compiler (on files whose compilation should succeed)"), - TestSet("neg", fileFilter, "neg", - "Testing compiler (on files whose compilation should fail)"), - TestSet("run", fileFilter, "run", "Testing JVM backend"), - TestSet("jvm", fileFilter, "jvm", "Testing JVM backend"), - TestSet("res", Some((".res", false)), "res", - "Testing resident compiler"), - TestSet("shootout", fileFilter, "shootout", "Testing shootout tests"), - TestSet("script", fileFilter, "script", "Testing script tests"), - TestSet("scalacheck", fileFilter, "scalacheck", "Testing ScalaCheck tests"), - TestSet("scalap", fileFilter, "scalap", "Run scalap decompiler tests")) - } - - var fileManager: ConsoleFileManager = _ - - private val isJava5 = javaVersion matches "1.[5|6|7].*" - private var runAll = false - private var testFiles: List[File] = List() - private val errors = - Integer.parseInt(System.getProperty("scalatest.errors", "0")) - - def denotesTestSet(arg: String) = - testSets exists { set => arg == "--" + set.loc } - - def denotesTestFile(arg: String) = - arg.endsWith(".scala") || arg.endsWith(".res") - - def denotesTestDir(arg: String) = - (new File(arg)).isDirectory - - private def printVersion { NestUI outline (versionMsg + "\n") } - - def main(argstr: String) { - // tokenize args. filter: "".split("\\s") yields Array("") - var args = (argstr split "\\s").toList.filterNot(_ == "") - - if (args.length == 0) - NestUI.usage() - else { - // find out which build to test - val (buildPath, args1) = searchAndRemovePath("--buildpath", args) - val (classPath, args2) = searchAndRemovePath("--classpath", args1) - val (srcPath, args3) = searchAndRemovePath("--srcpath", args2) - args = args3 - - if (!srcPath.isEmpty) - System.setProperty("partest.srcdir", srcPath.get) - - fileManager = - if (!buildPath.isEmpty) - new ConsoleFileManager(buildPath.get) - else if (!classPath.isEmpty) - new ConsoleFileManager(classPath.get, true) - else if (args contains "--pack") { - args = args.filterNot(_ == "--pack") // will create a result file '--pack' otherwise - new ConsoleFileManager("build/pack") - } else // auto detection, see ConsoleFileManager.findLatest - new ConsoleFileManager - - if (!args.exists(denotesTestSet(_)) && - !args.exists(denotesTestFile(_)) && - !args.exists(denotesTestDir(_))) - runAll = true - - var enabled = List[TestSet]() - var readTimeout = false - for (arg <- args) { - (testSets find { set => arg == "--" + set.loc }) match { - case Some(set) => enabled = set :: enabled - case None => arg match { - case "--all" => runAll = true - case "--verbose" => NestUI._verbose = true - case "--show-diff" => fileManager.showDiff = true - case "--show-log" => fileManager.showLog = true - case "--failed" => fileManager.failed = true - case "--version" => printVersion; return - case "--ansi" => NestUI.initialize(NestUI.MANY) - case "--timeout" => readTimeout = true - case s: String if readTimeout => - fileManager.timeout = s - readTimeout = false - case _ => - if (denotesTestFile(arg) || denotesTestDir(arg)) { - val file = new File(arg) - if (file.exists) { - NestUI.verbose("adding test file "+file) - testFiles = file :: testFiles - } else { - NestUI.failure("File \"" + arg + "\" not found\n") - System.exit(1) - } - } else { - NestUI.failure("Invalid option \""+arg+"\"\n") - NestUI.usage() - } - } - } - } - NestUI.verbose("enabled test sets: "+enabled) - NestUI.verbose("runAll: "+runAll) - - val dir = - if (!fileManager.testClasses.isEmpty) - fileManager.testClassesFile - else if (fileManager.testBuild != null) - fileManager.testBuildFile - else - fileManager.latestCompFile.getParentFile.getParentFile.getCanonicalFile - NestUI.outline("Scala compiler classes in: "+dir+"\n") - - NestUI.outline("Scala version is: "+scala.tools.nsc.Properties.versionMsg+"\n") - NestUI.outline("Scalac options are: "+fileManager.SCALAC_OPTS+"\n") - - val vmBin = javaHome + File.separator + "bin" - val vmName = "%s (build %s, %s)".format(javaVmName, javaVmVersion, javaVmInfo) - val vmOpts = fileManager.JAVA_OPTS - - NestUI.outline("Java binaries in: "+vmBin+"\n") - NestUI.outline("Java runtime is: "+vmName+"\n") - NestUI.outline("Java options are: "+vmOpts+"\n") - NestUI.outline("Source directory is: "+fileManager.srcDir.getAbsolutePath+"\n") - - val start = System.currentTimeMillis - - val (successes, failures) = testCheckAll(enabled) - - val end = System.currentTimeMillis - val total = successes + failures - - val elapsedSecs = (end - start)/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) - } - - println - 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") - - if (failures == errors) - System.exit(0) - else - System.exit(1) - } - } - - def runTests(testSet: TestSet): (Int, Int) = { - val TestSet(loc, filter, kind, msg) = testSet - val files = fileManager.getFiles(loc, true, filter) - if (!files.isEmpty) { - NestUI.verbose("test files: "+files) - NestUI.outline("\n"+msg+"\n") - runTestsForFiles(files, kind) - } else { - NestUI.verbose("test dir empty\n") - (0, 0) - } - } - - /** - * @return (success count, failure count) - */ - def testCheckAll(enabledSets: List[TestSet]): (Int, Int) = { - def runTestsFiles = if (!testFiles.isEmpty) { - def absName(f: File): String = f.getAbsoluteFile.getCanonicalPath - - def kindOf(f: File): String = { - val firstName = absName(f) - val len = fileManager.srcDirName.length - val filesPos = firstName.indexOf(fileManager.srcDirName) - if (filesPos == -1) { - NestUI.failure("invalid test file: "+firstName+"\n") - Predef.exit(1) - } else { - val short = firstName.substring(filesPos+len+1, filesPos+len+1+3) - val shortKinds = List("pos", "neg", "run", "jvm", "res") - if (shortKinds contains short) short - else short match { - case "sho" => "shootout" - case "scr" => "script" - case "sca" => "scalacheck" - } - } - } - - val fstKind = kindOf(testFiles.head) - NestUI.verbose("all test files expected to have kind "+fstKind) - if (!testFiles.forall(kindOf(_) equals fstKind)) { - NestUI.failure("test files have different kinds\n") - Predef.exit(1) - } else { - NestUI.outline("\nTesting individual files\n") - runTestsForFiles(testFiles, fstKind) - } - } else (0, 0) - - val runSets = - if (runAll) testSets // run all test sets - else enabledSets - NestUI.verbose("run sets: "+runSets) - - val results = List(runTestsFiles) ::: (runSets map runTests) - results reduceLeft { (p: (Int, Int), q: (Int, Int)) => - (p._1+q._1, p._2+q._2) } - } -} 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 7ea74424fc..0000000000 --- a/src/partest/scala/tools/partest/nest/DirectRunner.scala +++ /dev/null @@ -1,78 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -import java.io.{File, PrintStream, FileOutputStream, BufferedReader, - InputStreamReader, StringWriter, PrintWriter} -import java.util.StringTokenizer -import scala.tools.nsc.io.Directory - -import scala.actors.Actor._ -import scala.actors.TIMEOUT - -trait DirectRunner { - - def fileManager: FileManager - - private val numActors = Integer.parseInt(System.getProperty("scalatest.actors", "8")) - - if ((System.getProperty("partest.debug", "false") equals "true") || - (System.getProperty("scalatest.debug", "false") equals "true")) - scala.actors.Debug.level = 3 - - private val coreProp = try { - System.getProperty("actors.corePoolSize") - } catch { - case ace: java.security.AccessControlException => - null - } - if (coreProp == null) { - scala.actors.Debug.info("actors.corePoolSize not defined") - System.setProperty("actors.corePoolSize", "16") - } - - def runTestsForFiles(kindFiles: List[File], kind: String): (Int, Int) = { - val len = kindFiles.length - val (testsEach, lastFrag) = (len/numActors, len%numActors) - val last = numActors-1 - val workers = for (i <- List.range(0, numActors)) yield { - val toTest = kindFiles.slice(i*testsEach, (i+1)*testsEach) - val worker = new Worker(fileManager) - worker.start() - if (i == last) - worker ! RunTests(kind, (kindFiles splitAt (last*testsEach))._2) - else - worker ! RunTests(kind, toTest) - worker - } - var succs = 0; var fails = 0 - var logsToDelete: List[File] = List() - var outdirsToDelete: List[File] = List() - workers foreach { w => - receiveWithin(3600 * 1000) { - case Results(s, f, logs, outdirs) => - logsToDelete = logsToDelete ::: logs.filter(_.toDelete) - outdirsToDelete = outdirsToDelete ::: outdirs - succs += s - fails += f - case TIMEOUT => - // add at least one failure - NestUI.verbose("worker timed out; adding failed test") - fails += 1 - } - } - for (x <- logsToDelete ::: outdirsToDelete) { - NestUI.verbose("deleting "+x) - Directory(x).deleteRecursively() - } - - (succs, fails) - } - -} diff --git a/src/partest/scala/tools/partest/nest/FileManager.scala b/src/partest/scala/tools/partest/nest/FileManager.scala deleted file mode 100644 index 637999cc36..0000000000 --- a/src/partest/scala/tools/partest/nest/FileManager.scala +++ /dev/null @@ -1,79 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -import java.io.{File, FilenameFilter, IOException, StringWriter} -import java.net.URI -import scala.tools.nsc.io.Directory - -trait FileManager { - - def basename(name: String): String = { - val inx = name.lastIndexOf(".") - if (inx < 0) name else name.substring(0, inx) - } - - def deleteRecursive(dir: File) { Directory(dir).deleteRecursively() } - - /** - * Compares two files using a Java implementation of the GNU diff - * available at http://www.bmsi.com/java/#diff. - * - * @param f1 the first file to be compared - * @param f2 the second file to be compared - * @return the text difference between the compared files - */ - def compareFiles(f1: File, f2: File): String = { - var res = "" - try { - val diffWriter = new StringWriter - val args = Array(f1.getCanonicalPath(), f2.getCanonicalPath()) - DiffPrint.doDiff(args, diffWriter) - res = diffWriter.toString - if (res startsWith "No") - res = "" - } catch { - case e: IOException => - e.printStackTrace() - } - res - } - - var JAVACMD: String - var JAVAC_CMD: String - - var CLASSPATH: String - var LATEST_LIB: String - var LIB_DIR: String = "" - - val TESTROOT: String - - var showDiff = false - var showLog = false - var failed = false - - var SCALAC_OPTS = System.getProperty("scalatest.scalac_opts", "-deprecation") - var JAVA_OPTS = System.getProperty("scalatest.java_opts", "") - - var timeout = "1200000" - - def getLogFile(dir: File, fileBase: String, kind: String): LogFile = - new LogFile(dir, fileBase + "-" + kind + ".log") - - def getLogFile(file: File, kind: String): LogFile = { - val dir = file.getParentFile - val fileBase = basename(file.getName) - getLogFile(dir, fileBase, kind) - } - - def logFileExists(file: File, kind: String): Boolean = { - val logFile = getLogFile(file, kind) - logFile.exists && logFile.canRead - } -} diff --git a/src/partest/scala/tools/partest/nest/NestRunner.scala b/src/partest/scala/tools/partest/nest/NestRunner.scala deleted file mode 100644 index 158521875e..0000000000 --- a/src/partest/scala/tools/partest/nest/NestRunner.scala +++ /dev/null @@ -1,16 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -object NestRunner { - def main(args: Array[String]) { - val argstr = args.mkString(" ") - (new ReflectiveRunner).main(argstr) - } -} diff --git a/src/partest/scala/tools/partest/nest/NestUI.scala b/src/partest/scala/tools/partest/nest/NestUI.scala deleted file mode 100644 index b8d77bc704..0000000000 --- a/src/partest/scala/tools/partest/nest/NestUI.scala +++ /dev/null @@ -1,108 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -import java.io.PrintWriter - -object NestUI { - - val NONE = 0 - val SOME = 1 - val MANY = 2 - - private var _outline = "" - private var _success = "" - private var _failure = "" - private var _warning = "" - private var _default = "" - - def initialize(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 outline(msg: String) = print(_outline + msg + _default) - def outline(msg: String, wr: PrintWriter) = synchronized { - wr.print(_outline + msg + _default) - } - - def success(msg: String) = print(_success + msg + _default) - def success(msg: String, wr: PrintWriter) = synchronized { - wr.print(_success + msg + _default) - } - - def failure(msg: String) = print(_failure + msg + _default) - def failure(msg: String, wr: PrintWriter) = synchronized { - wr.print(_failure + msg + _default) - } - - 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 { - wr.print(_default + msg) - } - - 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 - println(" Test categories:") - println(" --all run all tests") - println(" --pos run compilation tests (success)") - println(" --neg run compilation tests (failure)") - println(" --run run interpreter and backend tests") - println(" --jvm run JVM backend tests") - println(" --res run resident compiler tests") - println(" --script run script runner tests") - println(" --shootout run shootout tests") - println - println(" Other options:") - println(" --pack pick compiler/library in build/pack, and run all tests") - println(" --show-log show log") - println(" --show-diff show diff between log and check file") - println(" --failed run only those tests that failed during the last run") - println(" --verbose show progress information") - println(" --buildpath set (relative) path to build jars") - println(" ex.: --buildpath build/pack") - println(" --classpath set (absolute) path to build classes") - println(" --srcpath set (relative) path to test source files") - println(" ex.: --srcpath pending") - println - println(utils.Properties.versionString) - println("maintained by Philipp Haller (EPFL)") - exit(1) - } - - var _verbose = false - - def verbose(msg: String) { - if (_verbose) { - outline("debug: ") - println(msg) - } - } - -} diff --git a/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala b/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala deleted file mode 100644 index bcedaa38be..0000000000 --- a/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala +++ /dev/null @@ -1,80 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -/* This class is used to load an instance of DirectRunner using - * a custom class loader. - * The purpose is to "auto-detect" a good classpath for the - * rest of the classes (Worker, CompileManager etc.), so that - * the main NestRunner can be started merely by putting its - * class on the classpath (ideally). - */ -class ReflectiveRunner extends RunnerUtils { - // TODO: we might also use fileManager.CLASSPATH - // to use the same classes as used by `scala` that - // was used to start the runner. - - import java.net.URLClassLoader - import java.io.File.pathSeparator - import utils.Properties.{ sysprop, syspropset } - - val sepRunnerClassName = "scala.tools.partest.nest.ConsoleRunner" - - def main(args: String) { - val argList = (args.split("\\s")).toList - - // find out which build to test - val buildPath = searchPath("--buildpath", argList) - val classPath = searchPath("--classpath", argList) - val fileManager = - if (!buildPath.isEmpty) - new ConsoleFileManager(buildPath.get) - else if (!classPath.isEmpty) - new ConsoleFileManager(classPath.get, true) - else if (argList contains "--pack") - new ConsoleFileManager("build/pack") - else // auto detection - new ConsoleFileManager - - import fileManager. - { latestCompFile, latestLibFile, latestActFile, latestPartestFile, latestFjbgFile } - val files = - Array(latestCompFile, latestLibFile, latestActFile, latestPartestFile, latestFjbgFile) - - val sepUrls = files map { _.toURI.toURL } - val sepLoader = new URLClassLoader(sepUrls, null) - - if (fileManager.debug) - println("Loading classes from:\n" + sepUrls.mkString("\n")) - - val paths = (if (classPath.isEmpty) files.slice(0, 4) else files) map { _.getPath } - val newClasspath = paths mkString pathSeparator - - syspropset("java.class.path", newClasspath) - syspropset("scala.home", "") - - if (fileManager.debug) - for (prop <- List("java.class.path", "sun.boot.class.path", "java.ext.dirs")) - println(prop + ": " + sysprop(prop)) - - try { - val sepRunnerClass = sepLoader loadClass sepRunnerClassName - val sepRunner = sepRunnerClass.newInstance() - val sepMainMethod = sepRunnerClass.getMethod("main", Array(classOf[String]): _*) - val cargs: Array[AnyRef] = Array(args) - sepMainMethod.invoke(sepRunner, cargs: _*) - } - catch { - case cnfe: ClassNotFoundException => - cnfe.printStackTrace() - NestUI.failure(sepRunnerClassName +" could not be loaded from:\n") - sepUrls foreach (x => NestUI.failure(x + "\n")) - } - } -} 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 4e41d00bf1..0000000000 --- a/src/partest/scala/tools/partest/nest/RunnerUtils.scala +++ /dev/null @@ -1,42 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -trait RunnerUtils { - - def searchPath(option: String, as: List[String]): Option[String] = { - val Option = option - as match { - case Option :: r :: rs => Some(r) - case other :: rest => searchPath(option, rest) - case List() => None - } - } - - def searchAndRemovePath(option: String, as: List[String]): (Option[String], List[String]) = { - val Option = option - def search(before: List[String], after: List[String]): (Option[String], List[String]) = after match { - case Option :: r :: rs => (Some(r), before ::: rs) - case other :: rest => search(before ::: List(other), rest) - case List() => (None, before) - } - search(List(), as) - } - - def searchAndRemoveOption(option: String, as: List[String]): (Boolean, List[String]) = { - val Option = option - def search(before: List[String], after: List[String]): (Boolean, List[String]) = after match { - case Option :: rest => (true, before ::: rest) - case other :: rest => search(before ::: List(other), rest) - case List() => (false, before) - } - search(List(), as) - } - -} diff --git a/src/partest/scala/tools/partest/nest/StreamAppender.scala b/src/partest/scala/tools/partest/nest/StreamAppender.scala deleted file mode 100644 index c4636af323..0000000000 --- a/src/partest/scala/tools/partest/nest/StreamAppender.scala +++ /dev/null @@ -1,90 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -import java.io.{Writer, PrintWriter, Reader, BufferedReader, - IOException, InputStream, StringWriter, InputStreamReader, - OutputStreamWriter, StringReader, OutputStream} - -object StreamAppender { - - def apply(reader: BufferedReader, writer: Writer): StreamAppender = { - val pwriter = new PrintWriter(writer, true) - new StreamAppender(reader, pwriter) - } - - def apply(reader: Reader, writer: Writer): StreamAppender = - apply(new BufferedReader(reader), writer) - - def appendToString(in1: InputStream, in2: InputStream): String = { - val swriter1 = new StringWriter - val swriter2 = new StringWriter - val reader1 = new BufferedReader(new InputStreamReader(in1)) - val reader2 = new BufferedReader(new InputStreamReader(in2)) - val app1 = StreamAppender(reader1, swriter1) - val app2 = StreamAppender(reader2, swriter2) - - val async = new Thread(app2) - async.start() - app1.run() - async.join() - swriter1.toString + swriter2.toString - } -/* - private def inParallel(t1: Runnable, t2: Runnable, t3: Runnable) { - val thr1 = new Thread(t1) - val thr2 = new Thread(t2) - thr1.start() - thr2.start() - t3.run() - thr1.join() - thr2.join() - } -*/ - private def inParallel(t1: Runnable, t2: Runnable) { - val thr = new Thread(t2) - thr.start() - t1.run() - thr.join() - } - - def concat(in: InputStream, err: InputStream, out: OutputStream) = new Runnable { - override def run() { - val outWriter = new PrintWriter(new OutputStreamWriter(out), true) - val inApp = new StreamAppender(new BufferedReader(new InputStreamReader(in)), - outWriter) - val errStringWriter = new StringWriter - val errApp = StreamAppender(new BufferedReader(new InputStreamReader(err)), - errStringWriter) - inParallel(inApp, errApp) - - // append error string to out - val errStrApp = new StreamAppender(new BufferedReader(new StringReader(errStringWriter.toString)), - outWriter) - errStrApp.run() - } - } -} - -class StreamAppender(reader: BufferedReader, writer: PrintWriter) extends Runnable { - override def run() = runAndMap(identity) - - def runAndMap(f: String => String) { - try { - var line = reader.readLine() - while (line != null) { - writer.println(f(line)) - line = reader.readLine() - } - } catch { - case e: IOException => - e.printStackTrace() - } - } -} 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 7ffe11e5b6..0000000000 --- a/src/partest/scala/tools/partest/nest/TestFile.scala +++ /dev/null @@ -1,109 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -import java.io.{File, BufferedReader, FileReader} -import scala.tools.nsc.Settings - -class TestFile(kind: String, val file: File, val fileManager: FileManager, createOutDir: Boolean) { - val dir = file.getParentFile - val dirpath = dir.getAbsolutePath - val fileBase: String = basename(file.getName) - - // @mutates settings - protected def baseSettings(settings: Settings) { - settings.classpath.value = settings.classpath.value+ - File.pathSeparator+dirpath - if (createOutDir) - settings.outdir.value = { - val outDir = new File(dir, fileBase + "-" + kind + ".obj") - if (!outDir.exists) - outDir.mkdir() - outDir.toString - } - - // add additional flags found in 'testname.flags' - val flagsFile = new File(dir, fileBase + ".flags") - if (flagsFile.exists) { - val reader = new BufferedReader(new java.io.FileReader(flagsFile)) - val flags = reader.readLine - if (flags ne null) - settings.parseParams(settings.splitParams(flags)) - } - } - - def defineSettings(settings: Settings) { - baseSettings(settings) - } - - private def basename(name: String): String = { - val inx = name.lastIndexOf(".") - if (inx < 0) name else name.substring(0, inx) - } - - override def toString(): String = kind+" "+file -} - -case class PosTestFile(override val file: File, override val fileManager: FileManager, createOutDir: Boolean) extends TestFile("pos", file, fileManager, createOutDir) { - override def defineSettings(settings: Settings) { - baseSettings(settings) - settings.classpath.value = settings.classpath.value+ - File.pathSeparator+fileManager.CLASSPATH - } -} - -case class NegTestFile(override val file: File, override val fileManager: FileManager, createOutDir: Boolean) extends TestFile("neg", file, fileManager, createOutDir) { - override def defineSettings(settings: Settings) { - baseSettings(settings) - settings.classpath.value = settings.classpath.value+ - File.pathSeparator+fileManager.CLASSPATH - } -} - -case class RunTestFile(override val file: File, override val fileManager: FileManager, createOutDir: Boolean) extends TestFile("run", file, fileManager, createOutDir) { - override def defineSettings(settings: Settings) { - baseSettings(settings) - settings.classpath.value = settings.classpath.value+ - File.pathSeparator+fileManager.CLASSPATH - } -} - -case class ScalaCheckTestFile(override val file: File, override val fileManager: FileManager, createOutDir: Boolean) extends TestFile("scalacheck", file, fileManager, createOutDir) { - override def defineSettings(settings: Settings) { - baseSettings(settings) - settings.classpath.value = settings.classpath.value+ - File.pathSeparator+fileManager.CLASSPATH - } -} - -case class JvmTestFile(override val file: File, override val fileManager: FileManager, createOutDir: Boolean) extends TestFile("jvm", file, fileManager, createOutDir) { - override def defineSettings(settings: Settings) { - baseSettings(settings) - settings.classpath.value = settings.classpath.value+ - File.pathSeparator+fileManager.CLASSPATH - } -} - -case class ShootoutTestFile(override val file: File, override val fileManager: FileManager, createOutDir: Boolean) extends TestFile("shootout", file, fileManager, createOutDir) { - override def defineSettings(settings: Settings) { - baseSettings(settings) - settings.classpath.value = settings.classpath.value+ - File.pathSeparator+fileManager.CLASSPATH - settings.outdir.value = file.getParent - } -} - -case class ScalapTestFile(override val file: File, override val fileManager: FileManager, createOutDir: Boolean) extends TestFile("scalap", file, fileManager, createOutDir) { - override def defineSettings(settings: Settings) { - baseSettings(settings) - settings.classpath.value = settings.classpath.value+ - File.pathSeparator+fileManager.CLASSPATH - settings.outdir.value = file.getParent - } -} diff --git a/src/partest/scala/tools/partest/nest/Worker.scala b/src/partest/scala/tools/partest/nest/Worker.scala deleted file mode 100644 index 37a98860ad..0000000000 --- a/src/partest/scala/tools/partest/nest/Worker.scala +++ /dev/null @@ -1,1000 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -import java.io._ -import java.net.{URLClassLoader, URL} -import java.util.{Timer, TimerTask} - -import scala.tools.nsc.{ObjectRunner, GenericRunnerCommand} -import scala.tools.nsc.io - -import scala.actors.{Actor, Exit, TIMEOUT} -import scala.actors.Actor._ -import scala.tools.scalap.scalax.rules.scalasig.{ByteCode, ClassFileParser, ScalaSigAttributeParsers} - -import scala.collection.mutable.HashMap - -case class RunTests(kind: String, files: List[File]) -case class Results(succ: Int, fail: Int, logs: List[LogFile], outdirs: List[File]) -case class LogContext(file: LogFile, writers: Option[(StringWriter, PrintWriter)]) - -abstract class TestResult { - def file: File -} -case class Result(override val file: File, context: LogContext) extends TestResult -case class Timeout(override val file: File) extends TestResult - -class LogFile(parent: File, child: String) extends File(parent, child) { - var toDelete = false -} - -class Worker(val fileManager: FileManager) extends Actor { - import fileManager._ - import scala.tools.nsc.{Settings, CompilerCommand, Global} - import scala.tools.nsc.reporters.ConsoleReporter - import scala.tools.nsc.util.FakePos - - var reporter: ConsoleReporter = _ - val timer = new Timer - - def error(msg: String) { - reporter.error(FakePos("scalac"), - msg + "\n scalac -help gives more information") - } - - def act() { - react { - case RunTests(kind, files) => - NestUI.verbose("received "+files.length+" to test") - val master = sender - runTests(kind, files, (succ: Int, fail: Int) => { - master ! Results(succ, fail, createdLogFiles, createdOutputDirs) - }) - } - } - - private def basename(name: String): String = { - val inx = name.lastIndexOf(".") - if (inx < 0) name else name.substring(0, inx) - } - - 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 testPathLen = testdir.getAbsolutePath.length - val name = file.getAbsolutePath.substring(testPathLen) - if (name.length <= totalWidth) - name - // 2. try with [...]/run/test.scala - else { - val filesPathLen = filesdir.getAbsolutePath.length - file.getAbsolutePath.substring(filesPathLen) - } - } - NestUI.normal("[...]"+name+(List.fill(totalWidth-name.length)(' ')).mkString, printer) - } - - def printInfoEnd(success: Boolean, printer: PrintWriter) { - NestUI.normal("[", printer) - if (success) NestUI.success(" OK ", printer) - else NestUI.failure("FAILED", printer) - NestUI.normal("]\n", printer) - } - - def printInfoTimeout(printer: PrintWriter) { - NestUI.normal("[", printer) - NestUI.failure("TIMOUT", printer) - NestUI.normal("]\n", printer) - } - - var log = "" - var createdLogFiles: List[LogFile] = List() - var createdOutputDirs: List[File] = List() - - def createLogFile(file: File, kind: String): LogFile = { - val logFile = fileManager.getLogFile(file, kind) - createdLogFiles = logFile :: createdLogFiles - logFile - } - - def createOutputDir(dir: File, fileBase: String, kind: String): File = { - val outDir = io.Path(dir) / io.Directory("%s-%s.obj".format(fileBase, kind)) - outDir.createDirectory() - createdOutputDirs ::= outDir.jfile - outDir.jfile - } - - /* Note: not yet used/tested. */ - def execTestObjectRunner(file: File, outDir: File, logFile: File) { - val consFM = new ConsoleFileManager - import consFM.{latestCompFile, latestLibFile, latestActFile, - latestPartestFile} - - val classpath: List[URL] = - outDir.toURI.toURL :: - //List(file.getParentFile.toURI.toURL) ::: - List(latestCompFile.toURI.toURL, latestLibFile.toURI.toURL, latestActFile.toURI.toURL, latestPartestFile.toURI.toURL) ::: - ((CLASSPATH split File.pathSeparatorChar).toList map (x => new File(x).toURI.toURL)) - - NestUI.verbose("ObjectRunner classpath: "+classpath) - - try { - // configure input/output files - val logOut = new FileOutputStream(logFile) - val logWriter = new PrintStream(logOut) - - // grab global lock - fileManager.synchronized { - - val oldStdOut = System.out - val oldStdErr = System.err - System.setOut(logWriter) - System.setErr(logWriter) - - /* - " -Djava.library.path="+logFile.getParentFile.getAbsolutePath+ - " -Dscalatest.output="+outDir.getAbsolutePath+ - " -Dscalatest.lib="+LATEST_LIB+ - " -Dscalatest.cwd="+outDir.getParent+ - " -Djavacmd="+JAVACMD+ - */ - - System.setProperty("java.library.path", logFile.getParentFile.getCanonicalFile.getAbsolutePath) - System.setProperty("scalatest.output", outDir.getCanonicalFile.getAbsolutePath) - System.setProperty("scalatest.lib", LATEST_LIB) - System.setProperty("scalatest.cwd", outDir.getParent) - - ObjectRunner.run(classpath, "Test", List("jvm")) - - logWriter.flush() - logWriter.close() - - System.setOut(oldStdOut) - System.setErr(oldStdErr) - } - - /*val out = new FileOutputStream(logFile, true) - Console.withOut(new PrintStream(out)) { - ObjectRunner.run(classpath, "Test", List("jvm")) - } - out.flush - out.close*/ - } catch { - case e: Exception => - NestUI.verbose(e+" ("+file.getPath+")") - e.printStackTrace() - } - } - - def javac(outDir: File, files: List[File], output: File): Boolean = { - // compile using command-line javac compiler - val javacCmd = if ((fileManager.JAVAC_CMD.indexOf("${env.JAVA_HOME}") != -1) || - fileManager.JAVAC_CMD.equals("/bin/javac") || - fileManager.JAVAC_CMD.equals("\\bin\\javac")) - "javac" - else - fileManager.JAVAC_CMD - - val cmd = javacCmd+ - " -d "+outDir.getAbsolutePath+ - " -classpath "+outDir+File.pathSeparator+CLASSPATH+ - " "+files.mkString(" ") - - val (success, msg) = try { - val exitCode = runCommand(cmd, output) - NestUI.verbose("javac returned exit code: "+exitCode) - if (exitCode != 0) - (false, "Running \"javac\" failed with exit code: "+exitCode+"\n"+cmd+"\n") - else - (true, "") - } catch { - case e: Exception => - val swriter = new StringWriter - e.printStackTrace(new PrintWriter(swriter)) - (false, "Running \"javac\" failed:\n"+cmd+"\n"+swriter.toString+"\n") - } - if (!success) { - val writer = new PrintWriter(new FileWriter(output, true), true) - writer.print(msg) - writer.close() - } - success - } - - /** Runs <code>command</code> redirecting standard out and - * error out to <code>output</code> file. - */ - def runCommand(command: String, output: File): Int = { - NestUI.verbose("running command:\n"+command) - val proc = Runtime.getRuntime.exec(command) - val in = proc.getInputStream - val err = proc.getErrorStream - val writer = new PrintWriter(new FileWriter(output), true) - val inApp = new StreamAppender(new BufferedReader(new InputStreamReader(in)), - writer) - val errApp = new StreamAppender(new BufferedReader(new InputStreamReader(err)), - writer) - val async = new Thread(errApp) - async.start() - inApp.run() - async.join() - writer.close() - try { - proc.exitValue() - } catch { - case e: IllegalThreadStateException => 0 - } - } - - def execTest(outDir: File, logFile: File, fileBase: String) { - // check whether there is a ".javaopts" file - val argsFile = new File(logFile.getParentFile, fileBase+".javaopts") - val argString = if (argsFile.exists) { - NestUI.verbose("Found javaopts file: "+argsFile) - val fileReader = new FileReader(argsFile) - val reader = new BufferedReader(fileReader) - val options = reader.readLine() - reader.close() - NestUI.verbose("Found javaopts file '%s', using options: '%s'".format(argsFile, options)) - options - } else "" - - val cp = System.getProperty("java.class.path", ".") - NestUI.verbose("java.class.path: "+cp) - - def quote(path: String) = "\""+path+"\"" - - // 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 - // scalatest.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 cmd = - JAVACMD+ - " "+JAVA_OPTS+ - " "+argString+ - " -classpath "+outDir+File.pathSeparator+CLASSPATH+ - " -Djava.library.path="+logFile.getParentFile.getAbsolutePath+ - " -Dscalatest.output="+outDir.getAbsolutePath+ - " -Dscalatest.lib="+LATEST_LIB+ - " -Dscalatest.cwd="+outDir.getParent+ - " -Djavacmd="+JAVACMD+ - " -Duser.language=en -Duser.country=US"+ - " scala.tools.nsc.MainGenericRunner"+ - " Test jvm" - NestUI.verbose(cmd) - - runCommand(cmd, logFile) - - if (fileManager.showLog) { - // produce log as string in `log` - val reader = new BufferedReader(new FileReader(logFile)) - val swriter = new StringWriter - val pwriter = new PrintWriter(swriter, true) - val appender = new StreamAppender(reader, pwriter) - appender.run() - log = swriter.toString - } - } - - def existsCheckFile(dir: File, fileBase: String, kind: String) = { - val checkFile = { - val chkFile = new File(dir, fileBase + ".check") - if (chkFile.isFile) - chkFile - else - new File(dir, fileBase + "-" + kind + ".check") - } - checkFile.exists && checkFile.canRead - } - - def compareOutput(dir: File, fileBase: String, kind: String, logFile: File): String = { - def getCheckFile(s: String) = { - val f = io.Path(dir) / io.File("%s%s.check".format(fileBase, s)) - if (f.isFile && f.canRead) Some(f) else None - } - - // if check file exists, compare with log file - (getCheckFile("") orElse getCheckFile("-" + kind)) match { - case Some(f) => fileManager.compareFiles(logFile, f.jfile) - case _ => file2String(logFile) - } - } - - def file2String(logFile: File) = io.File(logFile).slurp() - - /** Runs a list of tests. - * - * @param kind The test kind (pos, neg, run, etc.) - * @param files The list of test files - */ - def runTests(kind: String, files: List[File], topcont: (Int, Int) => Unit) { - val compileMgr = new CompileManager(fileManager) - var errors = 0 - var succeeded = true - var diff = "" - var log = "" - - /** 1. Creates log file and output directory. - * 2. Runs <code>script</code> function, providing log file and - * output directory as arguments. - */ - def runInContext(file: File, kind: String, script: (File, File) => Unit): LogContext = { - // when option "--failed" is provided - // execute test only if log file is present - // (which means it failed before) - val logFile = createLogFile(file, kind) - if (!fileManager.failed || (logFile.exists && logFile.canRead)) { - val swr = new StringWriter - val wr = new PrintWriter(swr) - succeeded = true - diff = "" - log = "" - printInfoStart(file, wr) - - val fileBase: String = basename(file.getName) - NestUI.verbose(this+" running test "+fileBase) - val dir = file.getParentFile - val outDir = createOutputDir(dir, fileBase, kind) - NestUI.verbose("output directory: "+outDir) - - // run test-specific code - try { - script(logFile, outDir) - } catch { - case e: Exception => - val writer = new PrintWriter(new FileWriter(logFile), true) - e.printStackTrace(writer) - writer.close() - succeeded = false - } - - LogContext(logFile, Some((swr, wr))) - } else - LogContext(logFile, None) - } - - def compileFilesIn(dir: File, kind: String, logFile: File, outDir: File) { - val testFiles = dir.listFiles.toList - - val groups = for (i <- 0 to 9) yield testFiles filter { f => - f.getName.endsWith("_"+i+".java") || - f.getName.endsWith("_"+i+".scala") } - - val noSuffix = testFiles filter { f => - !groups.exists(_ contains f) && ( - f.getName.endsWith(".java") || - f.getName.endsWith(".scala")) } - - def compileGroup(g: List[File]) { - val scalaFiles = g.filter(_.getName.endsWith(".scala")) - val javaFiles = g.filter(_.getName.endsWith(".java")) - - if (!scalaFiles.isEmpty && - !compileMgr.shouldCompile(outDir, - javaFiles ::: scalaFiles, - kind, logFile)) { - NestUI.verbose("scalac: compilation of "+g+" failed\n") - succeeded = false - } - - if (succeeded && !javaFiles.isEmpty) { - succeeded = javac(outDir, javaFiles, logFile) - if (succeeded && !scalaFiles.isEmpty - && !compileMgr.shouldCompile(outDir, - scalaFiles, - kind, logFile)) { - NestUI.verbose("scalac: compilation of "+scalaFiles+" failed\n") - succeeded = false - } - } - } - - if (!noSuffix.isEmpty) - compileGroup(noSuffix) - for (grp <- groups) { - if (succeeded) - compileGroup(grp) - } - } - - def failCompileFilesIn(dir: File, kind: String, logFile: File, outDir: File) { - val testFiles = dir.listFiles.toList - val javaFiles = testFiles.filter(_.getName.endsWith(".java")) - val scalaFiles = testFiles.filter(_.getName.endsWith(".scala")) - if (!(scalaFiles.isEmpty && javaFiles.isEmpty) && - !compileMgr.shouldFailCompile(outDir, javaFiles ::: scalaFiles, kind, logFile)) { - NestUI.verbose("compilation of "+scalaFiles+" failed\n") - succeeded = false - } - } - - def runJvmTest(file: File, kind: String): LogContext = - runInContext(file, kind, (logFile: File, outDir: File) => { - if (file.isDirectory) { - compileFilesIn(file, kind, logFile, outDir) - } else if (!compileMgr.shouldCompile(List(file), kind, logFile)) { - NestUI.verbose("compilation of "+file+" failed\n") - succeeded = false - } - if (succeeded) { // run test - val fileBase = basename(file.getName) - val dir = file.getParentFile - - //TODO: detect whether we have to use Runtime.exec - val useRuntime = true - - if (useRuntime) - execTest(outDir, logFile, fileBase) - else - execTestObjectRunner(file, outDir, logFile) - NestUI.verbose(this+" finished running "+fileBase) - - diff = compareOutput(dir, fileBase, kind, logFile) - if (!diff.equals("")) { - NestUI.verbose("output differs from log file\n") - succeeded = false - } - } - }) - - def processSingleFile(file: File): LogContext = kind match { - case "scalacheck" => - runInContext(file, kind, (logFile: File, outDir: File) => { - if (file.isDirectory) { - compileFilesIn(file, kind, logFile, outDir) - } else if (!compileMgr.shouldCompile(List(file), kind, logFile)) { - NestUI.verbose("compilation of "+file+" failed\n") - succeeded = false - } - if (succeeded) { - val consFM = new ConsoleFileManager - import consFM.{latestCompFile, latestLibFile, latestActFile, - latestPartestFile} - - NestUI.verbose("compilation of "+file+" succeeded\n") - - val libs = new File(fileManager.LIB_DIR) - val scalacheckURL = (new File(libs, "ScalaCheck.jar")).toURI.toURL - val outURL = outDir.getCanonicalFile.toURI.toURL - val classpath: List[URL] = - List(outURL, scalacheckURL, latestCompFile.toURI.toURL, latestLibFile.toURI.toURL, - latestActFile.toURI.toURL, latestPartestFile.toURI.toURL).removeDuplicates - - // XXX this is a big cut-and-paste mess, but the revamp is coming - val logOut = new FileOutputStream(logFile) - val logWriter = new PrintStream(logOut) - val oldStdOut = System.out - val oldStdErr = System.err - System.setOut(logWriter) - System.setErr(logWriter) - - ObjectRunner.run(classpath, "Test", Nil) - - logWriter.flush() - logWriter.close() - System.setOut(oldStdOut) - System.setErr(oldStdErr) - - NestUI.verbose(io.File(logFile).slurp()) - // obviously this must be improved upon - succeeded = io.File(logFile).lines() forall (_ contains " OK") - } - }) - - case "pos" => - runInContext(file, kind, (logFile: File, outDir: File) => { - if (file.isDirectory) { - compileFilesIn(file, kind, logFile, outDir) - } else if (!compileMgr.shouldCompile(List(file), kind, logFile)) { - NestUI.verbose("compilation of "+file+" failed\n") - succeeded = false - } - }) - - case "neg" => - runInContext(file, kind, (logFile: File, outDir: File) => { - if (file.isDirectory) { - failCompileFilesIn(file, kind, logFile, outDir) - } else if (!compileMgr.shouldFailCompile(List(file), kind, logFile)) { - succeeded = false - } - if (succeeded) { // compare log file to check file - val fileBase = basename(file.getName) - val dir = file.getParentFile - if (!existsCheckFile(dir, fileBase, kind)) { - // diff is contents of logFile - diff = file2String(logFile) - } else - diff = compareOutput(dir, fileBase, kind, logFile) - - if (!diff.equals("")) { - NestUI.verbose("output differs from log file\n") - succeeded = false - } - } - }) - - case "run" => - runJvmTest(file, kind) - - case "jvm" => - runJvmTest(file, kind) - - case "res" => { - // when option "--failed" is provided - // execute test only if log file is present - // (which means it failed before) - - //val (logFileOut, logFileErr) = createLogFiles(file, kind) - val logFile = createLogFile(file, kind) - if (!fileManager.failed || (logFile.exists && logFile.canRead)) { - val swr = new StringWriter - val wr = new PrintWriter(swr) - succeeded = true; diff = ""; log = "" - printInfoStart(file, wr) - - val fileBase: String = basename(file.getName) - NestUI.verbose(this+" running test "+fileBase) - val dir = file.getParentFile - val outDir = createOutputDir(dir, fileBase, kind) - if (!outDir.exists) outDir.mkdir() - 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 . "$@" - - try { - - val sourcedir = logFile.getParentFile.getCanonicalFile - val sourcepath = sourcedir.getAbsolutePath+File.separator - NestUI.verbose("sourcepath: "+sourcepath) - - val argString = - "-d "+outDir.getCanonicalFile.getAbsolutePath+ - " -Xresident"+ - " -sourcepath "+sourcepath - val argList = argString split ' ' toList - - // configure input/output files - val logOut = new FileOutputStream(logFile) - val logWriter = new PrintStream(logOut) - val resReader = new BufferedReader(new FileReader(resFile)) - val logConsoleWriter = new PrintWriter(new OutputStreamWriter(logOut)) - - // create compiler - val settings = new Settings(error) - settings.sourcepath.value = sourcepath - settings.classpath.value = fileManager.CLASSPATH - reporter = new ConsoleReporter(settings, scala.Console.in, logConsoleWriter) - val command = new CompilerCommand(argList, settings, error, false) - object compiler extends Global(command.settings, reporter) - - // simulate resident compiler loop - val prompt = "\nnsc> " - - 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(error) - sett.sourcepath.value = sourcepath - val command = new CompilerCommand(cmdArgs, sett, error, true) - (new compiler.Run) compile command.files - } - - def loop(action: (String) => Unit) { - logWriter.print(prompt) - val line = resReader.readLine() - if ((line ne null) && line.length() > 0) { -/* - val parent = self - self.trapExit = true - val child = link { - action(line) - } - - receiveWithin(fileManager.timeout.toLong) { - case TIMEOUT => - NestUI.verbose("action timed out") - false - case Exit(from, reason) if from == child => reason match { - case 'normal => // do nothing - case t: Throwable => - NestUI.verbose("while invoking compiler:") - NestUI.verbose("caught "+t) - t.printStackTrace - if (t.getCause != null) - t.getCause.printStackTrace - false - } - } -*/ - action(line) - loop(action) - } - } - val oldStdOut = System.out - val oldStdErr = System.err - System.setOut(logWriter) - System.setErr(logWriter) - loop(resCompile) - resReader.close() - logWriter.flush() - logWriter.close() - - System.setOut(oldStdOut) - System.setErr(oldStdErr) - - val tempLogFile = new File(dir, fileBase+".temp.log") - val logFileReader = new BufferedReader(new FileReader(logFile)) - val tempLogFilePrinter = new PrintWriter(new FileWriter(tempLogFile)) - val appender = - new StreamAppender(logFileReader, tempLogFilePrinter) - - // function that removes a given string from another string - def removeFrom(line: String, path: String): String = { - // find `path` in `line` - val index = line.indexOf(path) - if (index != -1) { - line.substring(0, index) + line.substring(index + path.length, line.length) - } else line - } - - appender.runAndMap({ s => - val woPath = removeFrom(s, dir.getAbsolutePath/*.replace(File.separatorChar,'/')*/+File.separator) - // now replace single '\' with '/' - woPath.replace('\\', '/') - }) - logFileReader.close() - tempLogFilePrinter.close() - - val tempLogFileReader = new BufferedReader(new FileReader(tempLogFile)) - val logFilePrinter= new PrintWriter(new FileWriter(logFile), true) - (new StreamAppender(tempLogFileReader, logFilePrinter)).run - tempLogFileReader.close() - logFilePrinter.close() - - tempLogFile.delete() - - diff = compareOutput(dir, fileBase, kind, logFile) - if (!diff.equals("")) { - NestUI.verbose("output differs from log file\n") - succeeded = false - } - - } catch { - case e: Exception => - e.printStackTrace() - succeeded = false - } - - LogContext(logFile, Some((swr, wr))) - } else - LogContext(logFile, None) - } - - case "shootout" => { - // when option "--failed" is provided - // execute test only if log file is present - // (which means it failed before) - val logFile = createLogFile(file, kind) - if (!fileManager.failed || (logFile.exists && logFile.canRead)) { - val swr = new StringWriter - val wr = new PrintWriter(swr) - succeeded = true; diff = ""; log = "" - printInfoStart(file, wr) - - val fileBase: String = basename(file.getName) - NestUI.verbose(this+" running test "+fileBase) - val dir = file.getParentFile - val outDir = createOutputDir(dir, fileBase, kind) - if (!outDir.exists) outDir.mkdir() - - // 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(dir, fileBase+".scala.runner") - val bodyFile = new File(dir, fileBase+".scala") - val appender = StreamAppender.concat(new FileInputStream(runnerFile), - new FileInputStream(bodyFile), - new FileOutputStream(testFile)) - appender.run() - - try { // *catch-all* - // 4. compile testFile - if (!compileMgr.shouldCompile(List(testFile), kind, logFile)) { - NestUI.verbose("compilation of "+file+" failed\n") - succeeded = false - } else { - NestUI.verbose("compilation of "+testFile+"succeeded") - // -------- run test -------- - - //TODO: detect whether we have to use Runtime.exec - val useRuntime = true - - if (useRuntime) - execTest(outDir, logFile, fileBase) - else - execTestObjectRunner(file, outDir, logFile) - NestUI.verbose(this+" finished running "+fileBase) - } // successful compile - } catch { // *catch-all* - case e: Exception => - NestUI.verbose("caught "+e) - succeeded = false - } - - diff = compareOutput(dir, fileBase, kind, logFile) - if (!diff.equals("")) { - NestUI.verbose("output differs from log file\n") - succeeded = false - } - - LogContext(logFile, Some((swr, wr))) - } else - LogContext(logFile, None) - } - - case "scalap" => { - - def decompileFile(clazz: Class[_], packObj: Boolean) = { - val byteCode = ByteCode.forClass(clazz) - val classFile = ClassFileParser.parse(byteCode) - val Some(sig) = classFile.attribute("ScalaSig").map(_.byteCode).map(ScalaSigAttributeParsers.parse) - import scala.tools.scalap.Main._ - parseScalaSignature(sig, packObj) - } - - runInContext(file, kind, (logFile: File, outDir: File) => { - val sourceDir = file.getParentFile - val sourceDirName = sourceDir.getName - - // 1. Find file with result text - val results = sourceDir.listFiles(new FilenameFilter { - def accept(dir: File, name: String) = name == "result.test" - }) - - if (results.length != 1) { - NestUI.verbose("Result file not found in directory " + sourceDirName + " \n") - } else { - val resFile = results(0) - // 2. Compile source file - if (!compileMgr.shouldCompile(outDir, List(file), kind, logFile)) { - NestUI.verbose("compilerMgr failed to compile %s to %s".format(file, outDir)) - succeeded = false - } else { - - // 3. Decompile file and compare results - val isPackageObject = sourceDir.getName.startsWith("package") - val className = sourceDirName.capitalize + (if (!isPackageObject) "" else ".package") - val url = outDir.toURI.toURL - val loader = new URLClassLoader(Array(url), getClass.getClassLoader) - val clazz = loader.loadClass(className) - - val result = decompileFile(clazz, isPackageObject) - - try { - val fstream = new FileWriter(logFile); - val out = new BufferedWriter(fstream); - out.write(result) - out.close(); - } catch { - case e: IOException => NestUI.verbose(e.getMessage()); succeeded = false - } - - val diff = fileManager.compareFiles(logFile, resFile) - if (!diff.equals("")) { - NestUI.verbose("output differs from log file\n") - succeeded = false - } - } - } - }) - } - - case "script" => { - val osName = System.getProperty("os.name", "") - // when option "--failed" is provided - // execute test only if log file is present - // (which means it failed before) - val logFile = createLogFile(file, kind) - if (!fileManager.failed || (logFile.exists && logFile.canRead)) { - val swr = new StringWriter - val wr = new PrintWriter(swr) - succeeded = true; diff = ""; log = "" - printInfoStart(file, wr) - - val fileBase: String = basename(file.getName) - 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 = if (argsFile.exists) { - val swriter = new StringWriter - val app = StreamAppender(new BufferedReader(new FileReader(argsFile)), - swriter) - app.run() - " "+swriter.toString - } else "" - - try { - val cmdString = - if (osName startsWith "Windows") { - val batchFile = new File(file.getParentFile, fileBase+".bat") - NestUI.verbose("batchFile: "+batchFile) - batchFile.getAbsolutePath - } - else file.getAbsolutePath - val proc = Runtime.getRuntime.exec(cmdString+argString) - val in = proc.getInputStream - val err = proc.getErrorStream - val writer = new PrintWriter(new FileWriter(logFile), true) - val inApp = new StreamAppender(new BufferedReader(new InputStreamReader(in)), - writer) - val errApp = new StreamAppender(new BufferedReader(new InputStreamReader(err)), - writer) - val async = new Thread(errApp) - async.start() - inApp.run() - async.join() - - writer.close() - - diff = compareOutput(file.getParentFile, fileBase, kind, logFile) - if (!diff.equals("")) { - NestUI.verbose("output differs from log file\n") - succeeded = false - } - } catch { // *catch-all* - case e: Exception => - NestUI.verbose("caught "+e) - succeeded = false - } - - LogContext(logFile, Some((swr, wr))) - } else - LogContext(logFile, None) - } - } - - def reportAll(cont: (Int, Int) => Unit) { - NestUI.verbose("finished testing "+kind+" with "+errors+" errors") - NestUI.verbose("created "+compileMgr.numSeparateCompilers+" separate compilers") - timer.cancel() - cont(files.length-errors, errors) - } - - def reportResult(logs: Option[LogContext]) { - if (!succeeded) { - errors += 1 - NestUI.verbose("incremented errors: "+errors) - } - - try { - // delete log file only if test was successful - if (succeeded && !logs.isEmpty) - logs.get.file.toDelete = true - - if (!logs.isEmpty) - logs.get.writers match { - case Some((swr, wr)) => - printInfoEnd(succeeded, wr) - wr.flush() - swr.flush() - NestUI.normal(swr.toString) - if (!succeeded && fileManager.showDiff && diff != "") - NestUI.normal(diff) - if (!succeeded && fileManager.showLog) - showLog(logs.get.file) - case None => - } - } catch { - case npe: NullPointerException => - } - } - - val numFiles = files.size - if (numFiles == 0) - reportAll(topcont) - - // maps canonical file names to the test result (0: OK, 1: FAILED, 2: TIMOUT) - val status = new HashMap[String, Int] - - var fileCnt = 1 - Actor.loopWhile(fileCnt <= numFiles) { - val parent = self - - actor { - val testFile = files(fileCnt-1) - - val ontimeout = new TimerTask { - def run() = parent ! Timeout(testFile) - } - timer.schedule(ontimeout, fileManager.timeout.toLong) - - val context = try { - processSingleFile(testFile) - } catch { - case t: Throwable => - NestUI.verbose("while invoking compiler ("+files+"):") - NestUI.verbose("caught "+t) - t.printStackTrace - if (t.getCause != null) - t.getCause.printStackTrace - LogContext(null, None) - } - parent ! Result(testFile, context) - } - - react { - case res: TestResult => - val path = res.file.getCanonicalPath - status.get(path) match { - case Some(stat) => // ignore message - case None => res match { - case Timeout(_) => - status += (path -> 2) - val swr = new StringWriter - val wr = new PrintWriter(swr) - printInfoStart(files(fileCnt-1), wr) - printInfoTimeout(wr) - wr.flush() - swr.flush() - NestUI.normal(swr.toString) - succeeded = false - reportResult(None) - if (fileCnt == numFiles) - reportAll(topcont) - fileCnt += 1 - case Result(_, logs) => - status += (path -> (if (succeeded) 0 else 1)) - reportResult(if (logs != null) Some(logs) else None) - if (fileCnt == numFiles) - reportAll(topcont) - fileCnt += 1 - } - } - } - } - } - - def showLog(logFile: File) { - try { - val logReader = new BufferedReader(new FileReader(logFile)) - val strWriter = new StringWriter - val logWriter = new PrintWriter(strWriter, true) - val logAppender = new StreamAppender(logReader, logWriter) - logAppender.run() - logReader.close() - val log = strWriter.toString - NestUI.normal(log) - } catch { - case fnfe: java.io.FileNotFoundException => - NestUI.failure("Couldn't open log file \""+logFile+"\".") - } - } -} diff --git a/src/partest/scala/tools/partest/package.scala b/src/partest/scala/tools/partest/package.scala new file mode 100644 index 0000000000..3ef4db7cd8 --- /dev/null +++ b/src/partest/scala/tools/partest/package.scala @@ -0,0 +1,47 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + */ + +package scala.tools + +import nsc.io.{ File, Path, Process, Directory } +import nsc.util.CommandLineSpec +import java.nio.charset.CharacterCodingException + +package object partest { + /** The CharacterCodingExceptions are thrown at least on windows trying + * to read a file like script/utf-8.scala + */ + private[partest] def safeSlurp(f: File) = + try if (f.exists) f.slurp() else "" + catch { case _: CharacterCodingException => "" } + + private[partest] def safeLines(f: File) = safeSlurp(f) split """\r\n|\r|\n""" toList + private[partest] def safeArgs(f: File) = toArgs(safeSlurp(f)) + private[partest] def safeToInt(s: String) = try Some(s.toInt) catch { case _: NumberFormatException => None } + private[partest] def isJava(f: Path) = f.isFile && (f hasExtension "java") + private[partest] def isScala(f: Path) = f.isFile && (f hasExtension "scala") + private[partest] def isJavaOrScala(f: Path) = isJava(f) || isScala(f) + + private[partest] def toArgs(line: String) = CommandLineSpec toArgs line + private[partest] def fromArgs(args: List[String]) = CommandLineSpec fromArgs args + + /** Strings, argument lists, etc. */ + + private[partest] def fromAnyArgs(args: List[Any]) = args mkString " " // separate to avoid accidents + private[partest] def toStringTrunc(x: Any, max: Int = 240) = { + val s = x.toString + if (s.length < max) s + else (s take max) + " [...]" + } + private[partest] def setProp(k: String, v: String) = scala.util.Properties.setProp(k, v) + + /** Pretty self explanatory. */ + def printAndExit(msg: String): Unit = { + println(msg) + exit(1) + } + + /** Apply a function and return the passed value */ + def returning[T](x: T)(f: T => Unit): T = { f(x) ; x } +}
\ No newline at end of file diff --git a/src/partest/scala/tools/partest/util/package.scala b/src/partest/scala/tools/partest/util/package.scala new file mode 100644 index 0000000000..bc5470ba5d --- /dev/null +++ b/src/partest/scala/tools/partest/util/package.scala @@ -0,0 +1,61 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + */ + +package scala.tools +package partest + +import java.util.{ Timer, TimerTask } +import java.io.StringWriter +import nsc.io._ + +/** Misc code still looking for a good home. + */ +package object util { + + def allPropertiesString() = javaHashtableToString(System.getProperties) + + private def javaHashtableToString(table: java.util.Hashtable[_,_]) = { + import collection.JavaConversions._ + (table.toList map { case (k, v) => "%s -> %s\n".format(k, v) }).sorted mkString + } + + def filesToSet(pre: String, fs: List[String]): Set[AbstractFile] = + fs flatMap (x => Option(AbstractFile getFile (Path(pre) / x).path)) toSet + + /** Copies one Path to another Path, trying to be sensible when one or the + * other is a Directory. Returns true if it believes it succeeded. + */ + def copyPath(from: Path, to: Path): Boolean = { + if (!to.parent.isDirectory) + to.parent.createDirectory(force = true) + + def copyDir = { + val sub = to / from.name createDirectory true + from.toDirectory.list forall (x => copyPath(x, sub)) + } + (from.isDirectory, to.isDirectory) match { + case (true, true) => copyDir + case (true, false) => false + case (false, true) => from.toFile copyTo (to / from.name) + case (false, false) => from.toFile copyTo to + } + } + + /** + * Compares two files using a Java implementation of the GNU diff + * available at http://www.bmsi.com/java/#diff. + * + * @param f1 the first file to be compared + * @param f2 the second file to be compared + * @return the text difference between the compared files + */ + def diffFiles(f1: File, f2: File): String = { + val diffWriter = new StringWriter + val args = Array(f1.toAbsolute.path, f2.toAbsolute.path) + + io.DiffPrint.doDiff(args, diffWriter) + val result = diffWriter.toString + if (result == "No differences") "" else result + } +} 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 10533130f1..0000000000 --- a/src/partest/scala/tools/partest/utils/PrintMgr.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala Parallel Testing ** -** / __/ __// _ | / / / _ | (c) 2007-2010, 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) -} |