diff options
author | Paul Phillips <paulp@improving.org> | 2010-04-05 06:25:16 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2010-04-05 06:25:16 +0000 |
commit | edc621d245520a3b8a9ceabeb06b5c31ace98ae0 (patch) | |
tree | 0afd97374527579ca2d9329fa530e0bfac9430f7 | |
parent | e8a121e9e1ade3f283f42fceb3c18f30a8468f57 (diff) | |
download | scala-edc621d245520a3b8a9ceabeb06b5c31ace98ae0.tar.gz scala-edc621d245520a3b8a9ceabeb06b5c31ace98ae0.tar.bz2 scala-edc621d245520a3b8a9ceabeb06b5c31ace98ae0.zip |
The code part of the partest patch.
it they can be my guest (reviewbot: review by community!) More
realistically: more than likely I have unwittingly altered or impaired
some piece of functionality used by someone somewhere. Please alert me
if this is the case and I will remedy it. I have to call it at this
point as the best interests of 2.8 cannot be served by me nursing this
patch along any further.
50 files changed, 2571 insertions, 2829 deletions
@@ -147,7 +147,9 @@ PROPERTIES <property name="lib.dir" value="${basedir}/lib"/> <property name="lib-ant.dir" value="${lib.dir}/ant"/> <property name="src.dir" value="${basedir}/src"/> - <property name="partest.dir" value="${basedir}/test"/> + <property name="partest.rootdir" location="test" /> + <property name="partest.srcdir.default" value="files" /> + <property name="partest.options" value="" /> <!-- Loads custom properties definitions --> <property file="${basedir}/build.properties"/> @@ -161,7 +163,7 @@ PROPERTIES <property name="comp.starr.jar" value="${lib.dir}/scala-compiler.jar"/> <property name="jline.jar" value="${lib.dir}/jline.jar"/> <property name="ant.jar" value="${ant.home}/lib/ant.jar"/> - <property name="scalacheck.jar" value="${lib.dir}/ScalaCheck.jar"/> + <property name="scalacheck.jar" value="${lib.dir}/scalacheck.jar"/> <!-- Sets location of build folders --> <property name="build.dir" value="${basedir}/build"/> @@ -176,7 +178,7 @@ PROPERTIES <property name="dists.dir" value="${basedir}/dists"/> <property name="copyright.string" value="Copyright 2002-2010, LAMP/EPFL"/> - <property name="partest.version.number" value="0.9.2"/> + <property name="partest.version.number" value="0.9.3"/> <!-- These are NOT the flags used to run SuperSabbus, but the ones written into the script runners created with scala.tools.ant.ScalaTool --> @@ -184,16 +186,11 @@ PROPERTIES <!-- if ANT_OPTS is already set by the environment, it will be unaltered, but if it is unset it will take this default value. --> - <property name="env.ANT_OPTS" value="-Xms512M -Xmx1536M -Xss1M -XX:MaxPermSize=128M" /> + <property name="env.ANT_OPTS" value="-Xms128M -Xmx1024M -Xss1M -XX:MaxPermSize=128M" /> <!-- to find max heap usage: -Xaprof ; currently at 980M for locker.comp --> - <echo message="Using ANT_OPTS: ${env.ANT_OPTS}" /> - <property - name="scalacfork.jvmargs" - value="${env.ANT_OPTS}"/> - - <property name="javac.cmd" value="${env.JAVA_HOME}/bin/javac"/> - <property name="java.cmd" value="${env.JAVA_HOME}/bin/java"/> + <property name="scalacfork.jvmargs" value="${env.ANT_OPTS}" /> + <echo message="Using scalacfork.jvmargs: ${scalacfork.jvmargs}" /> <!-- =========================================================================== INITIALISATION @@ -708,6 +705,7 @@ QUICK BUILD (QUICK) <pathelement location="${build-quick.dir}/classes/scalap"/> <pathelement location="${build-quick.dir}/classes/partest"/> <pathelement location="${ant.jar}"/> + <pathelement location="${scalacheck.jar}"/> </compilationpath> </scalacfork> <propertyfile file="${build-quick.dir}/classes/partest/partest.properties"> @@ -938,6 +936,9 @@ PACKED QUICK BUILD (PACK) <exec osfamily="unix" executable="${build-pack.dir}/bin/scala" output="${build-pack.dir}/etc/scala_completion.sh" failifexecutionfails="false" > <arg line="scala.tools.util.BashCompletion" /> </exec> + <!-- <exec append="true" osfamily="unix" executable="${build-pack.dir}/bin/scala" output="${build-pack.dir}/etc/scala_completion.sh" failifexecutionfails="false" > + <arg line="scala.tools.partest.PartestSpecDryRun" /> + </exec> --> <touch file="${build-pack.dir}/bin.complete" verbose="no"/> </target> @@ -1210,6 +1211,7 @@ BOOTSTRAPPING BUILD (STRAP) <pathelement location="${build-strap.dir}/classes/compiler"/> <pathelement location="${build-strap.dir}/classes/scalap"/> <pathelement location="${build-strap.dir}/classes/partest"/> + <pathelement location="${scalacheck.jar}"/> <pathelement location="${ant.jar}"/> </compilationpath> </scalacfork> @@ -1501,68 +1503,27 @@ BOOTRAPING TEST AND TEST SUITE <exclude name="plugins/*.jar"/> </same> </target> - - <!-- this target will run only those tests found in test/debug --> - <target name="test.debug"> - <antcall target="test.suite"> - <param name="partest.srcdir" value="debug" /> - </antcall> - </target> - - <target name="test.run" depends="pack.done"> - <partest showlog="yes" erroronfailed="yes" javacmd="${java.home}/bin/java" - timeout="1200000" javaccmd="${javac.cmd}" - scalacopts="${scalac.args.optimise}"> - <compilationpath> - <path refid="pack.classpath"/> - <fileset dir="${partest.dir}/files/lib" includes="*.jar" /> - </compilationpath> - <runtests dir="${partest.dir}/files"> - <include name="run/**/*.scala"/> - <include name="jvm/**/*.scala"/> - </runtests> - </partest> - </target> <target name="test.suite" depends="pack.done"> - <property name="partest.srcdir" value="files" /> - <partest showlog="yes" erroronfailed="yes" javacmd="${java.home}/bin/java" - timeout="2400000" javaccmd="${javac.cmd}" - srcdir="${partest.srcdir}" - scalacopts="${scalac.args.optimise}"> - <compilationpath> - <path refid="pack.classpath"/> - <fileset dir="${partest.dir}/files/lib" includes="*.jar" /> - </compilationpath> - <postests dir="${partest.dir}/${partest.srcdir}/pos" includes="*.scala"/> - <negtests dir="${partest.dir}/${partest.srcdir}/neg" includes="*.scala"/> - <runtests dir="${partest.dir}/${partest.srcdir}"> - <include name="run/**/*.scala"/> - </runtests> - <jvmtests dir="${partest.dir}/${partest.srcdir}/jvm" includes="*.scala"/> - <scalachecktests dir="${partest.dir}/${partest.srcdir}/scalacheck" includes="**/*.scala"/> - <residenttests dir="${partest.dir}/${partest.srcdir}/res" includes="*.res"/> - <buildmanagertests dir="${partest.dir}/${partest.srcdir}/buildmanager" includes="*"/> - <scalaptests dir="${partest.dir}/${partest.srcdir}/scalap" includes="**/*.scala"/> - <!-- <scripttests dir="${partest.dir}/${partest.srcdir}/script" includes="*.scala"/> --> + <partest classpathref="pack.classpath"> + <env key="PATH" path="${build-pack.dir}/bin:${env.PATH}" /> + <sysproperty key="partest.srcdir" value="files" /> + <syspropertyset> + <propertyref prefix="partest"/> + </syspropertyset> </partest> </target> <target name="test.continuations.suite" depends="pack.done"> - <property name="partest.srcdir" value="files" /> - <partest showlog="yes" erroronfailed="yes" javacmd="${java.home}/bin/java" - timeout="2400000" javaccmd="${javac.cmd}" - srcdir="${partest.srcdir}" - scalacopts="${scalac.args.optimise} -Xpluginsdir ${build-quick.dir}/plugins -Xplugin-require:continuations -P:continuations:enable"> - <compilationpath> - <path refid="pack.classpath"/> - <fileset dir="${partest.dir}/files/lib" includes="*.jar" /> - </compilationpath> - <negtests dir="${partest.dir}/${partest.srcdir}/continuations-neg" includes="*.scala"/> - <runtests dir="${partest.dir}/${partest.srcdir}"> - <include name="continuations-run/**/*.scala"/> - </runtests> - </partest> + <partest classpathref="pack.classpath"> + <env key="PATH" path="${build-pack.dir}/bin:${env.PATH}" /> + <sysproperty key="partest.srcdir" value="continuations" /> + <sysproperty key="partest.scalacopts" value="${scalac.args.optimise} -Xpluginsdir ${build-quick.dir}/plugins -Xplugin-require:continuations -P:continuations:enable" /> + <sysproperty key="partest.runsets" value="neg run" /> + <syspropertyset> + <propertyref prefix="partest"/> + </syspropertyset> + </partest> </target> <target name="test.done" depends="test.suite, test.continuations.suite, test.stability"/> @@ -1775,7 +1736,7 @@ POSITIONS <target name="test.positions" depends="quick.comp"> <antcall target="test.positions.tests.sub" inheritRefs="true"> - <param name="test.tests.srcs" value="${partest.dir}/files/positions"/> + <param name="test.tests.srcs" value="${partest.rootdir}/${partest.srcdir.default}/positions"/> </antcall> <antcall target="test.positions.sub" inheritRefs="true"> <param name="test.srcs" value="${src.dir}/compiler"/> @@ -1799,13 +1760,13 @@ POSITIONS <param name="test.srcs" value="${src.dir}/scalap"/> </antcall> <antcall target="test.positions.tests.sub" inheritRefs="true"> - <param name="test.tests.srcs" value="${partest.dir}/files/pos"/> + <param name="test.tests.srcs" value="${partest.rootdir}/${partest.srcdir.default}/pos"/> </antcall> <antcall target="test.positions.tests.sub" inheritRefs="true"> - <param name="test.tests.srcs" value="${partest.dir}/files/run"/> + <param name="test.tests.srcs" value="${partest.rootdir}/${partest.srcdir.default}/run"/> </antcall> <antcall target="test.positions.tests.sub" inheritRefs="true"> - <param name="test.tests.srcs" value="${partest.dir}/files/neg"/> + <param name="test.tests.srcs" value="${partest.rootdir}/${partest.srcdir.default}/neg"/> </antcall> </target> diff --git a/src/compiler/scala/tools/nsc/util/CommandLineParser.scala b/src/compiler/scala/tools/nsc/util/CommandLineParser.scala index 8c950322e0..869c6a97e9 100644 --- a/src/compiler/scala/tools/nsc/util/CommandLineParser.scala +++ b/src/compiler/scala/tools/nsc/util/CommandLineParser.scala @@ -25,19 +25,20 @@ trait ParserUtil extends Parsers { case class CommandLine( args: List[String], - unaryArguments: List[String], - binaryArguments: List[String] + unaryOptions: List[String], + binaryOptions: List[String] ) { def this(args: List[String]) = this(args, Nil, Nil) def this(args: Array[String]) = this(args.toList, Nil, Nil) def this(line: String) = this(CommandLineParser tokenize line, Nil, Nil) - def withUnaryArgs(xs: List[String]) = copy(unaryArguments = xs) - def withBinaryArgs(xs: List[String]) = copy(binaryArguments = xs) + def withUnary(xs: List[String]) = copy(unaryOptions = xs) + def withBinary(xs: List[String]) = copy(binaryOptions = xs) - def originalArgs = args - def assumeBinary = true - def enforceArity = true + def allOptions = unaryOptions ++ binaryOptions + def originalArgs = args + def assumeBinary = true + def enforceArity = true def onlyKnownOptions = false val Terminator = "--" @@ -57,10 +58,10 @@ case class CommandLine( if (List('"', '\'') exists isQuotedBy) s.tail.init else s } - def isValidOption(s: String) = !onlyKnownOptions || (unaryArguments contains s) || (binaryArguments contains s) + def isValidOption(s: String) = !onlyKnownOptions || (unaryOptions contains s) || (binaryOptions contains s) def isOption(s: String) = (s startsWith "-") && (isValidOption(s) || { unknownOption(s) ; false }) - def isUnary(s: String) = isOption(s) && (unaryArguments contains s) - def isBinary(s: String) = isOption(s) && !isUnary(s) && (assumeBinary || (binaryArguments contains s)) + def isUnary(s: String) = isOption(s) && (unaryOptions contains s) + def isBinary(s: String) = isOption(s) && !isUnary(s) && (assumeBinary || (binaryOptions contains s)) def unknownOption(opt: String) = errorFn("Option '%s' not recognized.".format(opt)) diff --git a/src/compiler/scala/tools/nsc/util/CommandLineSpec.scala b/src/compiler/scala/tools/nsc/util/CommandLineSpec.scala new file mode 100644 index 0000000000..4835506a47 --- /dev/null +++ b/src/compiler/scala/tools/nsc/util/CommandLineSpec.scala @@ -0,0 +1,131 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package util + +import Properties._ +import io._ +import CommandLineSpec._ +import CommandLineParser.tokenize + +/** This trait works together with CommandLine to allow declaratively + * specifying a command line program, with many attendant benefits. + * See scala.tools.partest.PartestSpec for a full example. + */ + +trait CommandLineSpec { + def parsed: CommandLine + def isReferenceSpec: Boolean = false + def isPassthroughProperty(name: String): Boolean = false + def isSysPropOption(key: String): Option[String] = None + + private var _helpMessage: String = "" + private var _unaryOptions: List[String] = Nil + private var _binaryOptions: List[String] = Nil + private def allOptions = if (isReferenceSpec) Nil else parsed.allOptions + private def longestArg = if (allOptions.isEmpty) 1 else allOptions map (_.length) max + private def unquoted(s: String) = { + def isQuoted = (s.head == '\'' || s.head == '"') && s.head == s.last + + if (s == null || s.length < 2 || !isQuoted) s + else s drop 1 dropRight 1 + } + + protected def help(str: String) = if (isReferenceSpec) () else _helpMessage += (str.stripMargin + "\n") + protected def heading(s: String) = if (isReferenceSpec) () else help("\n " + s) + + /** The various operators: + * val isCond1 = "cond1" ? // --cond1 is unary, cond1 is boolean + * "cond2" ?> body // --cond2 is unary, body is executed if it is given + * val val1 = "val1" |> "alt" // --val1 is binary, val1 is String, alt used if none given + * val val2 = "val2" >> // --val2 is binary, val2 is Option[String], None if none given + */ + protected class OptionStringAdditions(name: String) { + val s = toOpt(name) + def ? : Boolean = { _unaryOptions +:= s ; if (isReferenceSpec) false else parsed isSet s } + def ?>(body: => Unit): Unit = { _unaryOptions +:= s ; if (isReferenceSpec) () else if (parsed isSet s) body } + def |>(alt: String): String = { _binaryOptions +:= s ; if (isReferenceSpec) "" else parsed.getOrElse(s, alt) } + def >> : Option[String] = { _binaryOptions +:= s ; if (isReferenceSpec) None else parsed get s } + + def /(description: String) = { + val formatStr = " %-" + longestArg + "s %s" + help(formatStr.format(s, description)) + + name + } + } + protected implicit def stringAdditions(s: String) = new OptionStringAdditions(s) + + lazy val unaryOptions = _unaryOptions.distinct + lazy val binaryOptions = _binaryOptions.distinct + lazy val helpMsg = _helpMessage + + def isUnaryOption(s: String) = unaryOptions contains toOpt(s) + def isBinaryOption(s: String) = binaryOptions contains toOpt(s) + + private def sysPropToOptions(k: String, v: String): List[String] = { + if (isPassthroughProperty(k)) toArgs(v) + else isSysPropOption(k).toList flatMap { optName => + val opt = toOpt(optName) + + if (isUnaryOption(optName)) List(opt) + else if (isBinaryOption(optName)) List(opt, v) + else { + if (warnSuspiciousProperties) { + println("Warning, this looks like a command line option but I don't understand it.") + println("Ignoring: " + k + "=" + v) + } + Nil + } + } + } + def warnSuspiciousProperties: Boolean = true + def sysPropsAsOptions() = allSystemProperties.toList flatMap (sysPropToOptions _).tupled + + def isSet(s: String) = parsed isSet toOpt(s) + def reconstruct: List[String] = { + val unary = unaryOptions filter (parsed isSet _) + val binary = binaryOptions collect { case x if parsed isSet x => List(x, parsed(x)) } + val resid = parsed.residualArgs + + unary ++ binary.flatten ++ resid + } + + def bashCompletion(programName: String) = { + val opts = unaryOptions ++ binaryOptions + bashCompletionTemplate.replaceAll("@@PROGRAM@@", programName).replaceAll("@@OPTIONS@@", opts mkString " ") + } +} + +object CommandLineSpec { + def toOpt(s: String) = if (s startsWith "--") s else "--" + s + def fromOpt(s: String) = s stripPrefix "--" + def toArgs(line: String) = tokenize(line) + def fromArgs(args: List[String]) = args mkString " " + + def allSystemProperties: Map[String, String] = { + import collection.JavaConversions._ + + System.getProperties.toMap + } + + /** A very simple template for generating bash completion functions. + */ + val bashCompletionTemplate = """ + |_@@PROGRAM@@() + |{ + | local cur opts base + | COMPREPLY=() + | cur="${COMP_WORDS[COMP_CWORD]}" + | opts="@@OPTIONS@@" + | + | COMPREPLY=($(compgen -W "${opts}" -- ${cur})) + | _filedir + | return 0 + |} + |complete -F _@@PROGRAM@@ @@PROGRAM@@ + """.stripMargin +} diff --git a/src/partest/README b/src/partest/README index 81876fc810..c7673fe2f8 100644 --- a/src/partest/README +++ b/src/partest/README @@ -1,32 +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: - * ''-Dpartest.build=build/four-pack'' -> will search for libraries in - ''lib'' directory of given path - * ''--pack'' -> will set ''partest.build=build/pack'', and run all tests. - add ''--[kind]'' to run a selected set of tests. - * auto detection: - - partest.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 - * --buildmanager next files test the build manager - * --shootout next files are shootout tests - * --script next files test the script runner - * ''-Dpartest.scalac_opts=...'' -> add compiler options - * ''--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/BuildContributors.scala b/src/partest/scala/tools/partest/BuildContributors.scala new file mode 100644 index 0000000000..abd3bb318b --- /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) + 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..6be08ae8d2 --- /dev/null +++ b/src/partest/scala/tools/partest/Config.scala @@ -0,0 +1,116 @@ +/* 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, seems overly generous + def testWarningSecs = testWarning.toInt // test warning - 90s by default + def testTimeout = testWarningSecs * 10 // test timeout + 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" + + /** 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..e453e27293 --- /dev/null +++ b/src/partest/scala/tools/partest/Dispatcher.scala @@ -0,0 +1,198 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Philipp Haller + */ + +package scala.tools +package partest + +import util._ +import scala.tools.nsc.io._ +import scala.actors.{ Actor, Exit, TIMEOUT } +import scala.actors.Actor._ +import scala.collection.immutable +import scala.util.control.ControlThrowable + +/** 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 + val test = testIterator.next + + // Sets three alarms: two slowness warnings and a timeout. + def setAlarms() = + new Alarmer( + testWarningSecs -> (() => warning( + """|I've been waiting %d seconds for this to complete: + | "%s" + |Either it's stuck or it is unreasonably slow and should + |be divided into smaller tests. + |""".stripMargin.format(testWarning, test))), + + (testWarningSecs * 2) -> (() => warning( + """|Now I've been waiting %d seconds for this to complete: + | "%s" + |If partest seems hung, let's blame that one. + |""".stripMargin.format(testWarning * 2, test))), + + testTimeout -> (() => parent ! new Timeout(test)) + ) + + actor { + /** Debugging alarm issues */ + if (isNoAlarms) { + parent ! TestResult(test, test.isSuccess) + } + else { + // Set alarms, kick it off. Calling isSuccess forces the lazy val + // "process" inside the test, running it. + val alarmer = setAlarms() + def respondWith(res: TestResult) = { + // Cancel the alarms and alert the media. + alarmer.cancelAll() + parent ! res + } + + try respondWith(TestResult(test, test.isSuccess)) + catch { + case x: ControlThrowable => + test.warnAndLogException("Worker caught " + x + ", rethrowing: ", x) + throw x + case x => + test.warnAndLogException("Worker caught " + x + ", failing: ", x) + respondWith(TestResult(test, false)) + } + } + } + + 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..aea794d793 --- /dev/null +++ b/src/partest/scala/tools/partest/Entities.scala @@ -0,0 +1,78 @@ +/* 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 EntityLogging + with CompilableTest + with ScriptableTest + with DiffableTest { + def location: Path + def category: TestCategory + + lazy val label = location.stripExtension + lazy val testClasspath = createClasspathString() + + /** 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) + + // if outcome is empty, the JVM is trying to shut down, so we clam up + // to avoid echoing lots of spurious failure messages. + if (outcome.isEmpty && !isShuttingDown) setShuttingDown() + else 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..fdc04de7e3 --- /dev/null +++ b/src/partest/scala/tools/partest/Housekeeping.scala @@ -0,0 +1,168 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + */ + +package scala.tools +package partest + +import util._ +import nsc.io._ +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. */ + private var _shuttingDown = false + protected def setShuttingDown() = { + warning("Received shutdown signal, partest is cleaning up...\n") + _shuttingDown = true + false + } + def isShuttingDown = _shuttingDown + + /** 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() = + if (isNoCleanup && outDir.isDirectory) debug("Not deleting " + outDir) + else outDir.deleteRecursively() + + def cleanup() { + // otherwise, delete obj dir and logs on success + deleteOutDir() + if (isSuccess) + deleteLog() + } + + protected def runWrappers[T](body: => T): Option[T] = { + prepareForTestRun() + + withShutdownHook({ debug("Shutdown hook deleting " + outDir) ; deleteOutDir }) { + loggingOutAndErr { + possiblyTimed { + body + } + } + } + } + + 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..b3580d0f1c --- /dev/null +++ b/src/partest/scala/tools/partest/Partest.scala @@ -0,0 +1,75 @@ +/* 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 = new CommandLine(args, PartestSpecReference.unary, PartestSpecReference.binary) { + override def onlyKnownOptions = true + override def errorFn(msg: String) = printAndExit("Error: " + msg) + } +} 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/PartestDefaults.scala b/src/partest/scala/tools/partest/PartestDefaults.scala deleted file mode 100644 index 139c54dedd..0000000000 --- a/src/partest/scala/tools/partest/PartestDefaults.scala +++ /dev/null @@ -1,30 +0,0 @@ -package scala.tools -package partest - -import nsc.io.{ File, Path, Process, Directory } -import util.{ PathResolver } -import nsc.Properties.{ propOrElse, propOrNone, propOrEmpty } - -object PartestDefaults { - import nsc.Properties._ - private def wrapAccessControl[T](body: => Option[T]): Option[T] = - try body catch { case _: java.security.AccessControlException => None } - - def testRootName = propOrNone("partest.root") - def srcDirName = propOrElse("partest.srcdir", "files") - def testRootDir = testRootName map (x => Directory(x)) - - def classPath = PathResolver.Environment.javaUserClassPath // XXX - - def javaCmd = propOrElse("partest.javacmd", "java") - def javacCmd = propOrElse("partest.javac_cmd", "javac") - def javaOpts = propOrElse("partest.java_opts", "") - def scalacOpts = propOrElse("partest.scalac_opts", "-deprecation") - - def testBuild = propOrNone("partest.build") - def errorCount = propOrElse("partest.errors", "0").toInt - def numActors = propOrElse("partest.actors", "8").toInt - def poolSize = wrapAccessControl(propOrNone("actors.corePoolSize")) - - def timeout = "1200000" -} diff --git a/src/partest/scala/tools/partest/PartestSpec.scala b/src/partest/scala/tools/partest/PartestSpec.scala new file mode 100644 index 0000000000..55d2fdcca5 --- /dev/null +++ b/src/partest/scala/tools/partest/PartestSpec.scala @@ -0,0 +1,110 @@ +/* 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 } + +/** 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") ?> setProp("partest.build", "build/pack") + ("quick" / "alias for --builddir build/quick") ?> setProp("partest.build", "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" / "Timeout in seconds" >> ; + 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" ?) + val testWarning = "test-warning" |> "90" +} + +object PartestSpecReference extends PartestSpec { + import CommandLineSpec._ + + def parsed: CommandLine = null + override def isReferenceSpec = true + + def unary = unaryOptions + def binary = binaryOptions + def allArgs = unary ++ binary + + def isunaryOption(s: String) = unary contains toOpt(s) + def isbinaryOption(s: String) = binary contains toOpt(s) + + 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 96802370fc..0000000000 --- a/src/partest/scala/tools/partest/PartestTask.scala +++ /dev/null @@ -1,288 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala Parallel Testing ** -** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -// $Id$ - -package scala.tools -package partest - -import scala.actors.Actor._ -import scala.util.Properties.setProp -import scala.tools.nsc.io -import io.{ Directory } -import nsc.Settings -import nsc.util.ClassPath -import util.PathResolver -import scala.tools.ant.sabbus.CompilationPathProperty - -import java.io.File -import java.net.URLClassLoader -import java.lang.reflect.Method - -import org.apache.tools.ant.Task -import org.apache.tools.ant.types.{Path, Reference, FileSet} - -class PartestTask extends Task with CompilationPathProperty { - - def addConfiguredPosTests(input: FileSet) { - posFiles = Some(input) - } - - def addConfiguredNegTests(input: FileSet) { - negFiles = Some(input) - } - - def addConfiguredRunTests(input: FileSet) { - runFiles = Some(input) - } - - def addConfiguredJvmTests(input: FileSet) { - jvmFiles = Some(input) - } - - def addConfiguredResidentTests(input: FileSet) { - residentFiles = Some(input) - } - - def addConfiguredBuildManagerTests(input: FileSet) { - buildManagerFiles = Some(input) - } - - def addConfiguredScalacheckTests(input: FileSet) { - scalacheckFiles = Some(input) - } - - def addConfiguredScriptTests(input: FileSet) { - scriptFiles = Some(input) - } - - def addConfiguredShootoutTests(input: FileSet) { - shootoutFiles = Some(input) - } - - def addConfiguredScalapTests(input: FileSet) { - scalapFiles = Some(input) - } - - def setSrcDir(input: String) { - srcDir = 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 - } - - def setJUnitReportDir(input: File) { - jUnitReportDir = Some(input) - } - - private var classpath: Option[Path] = None - private var srcDir: Option[String] = None - private var javacmd: Option[File] = None - private var javaccmd: Option[File] = None - private var showDiff: Boolean = false - private var showLog: Boolean = false - private var runFailed: Boolean = false - private var posFiles: Option[FileSet] = None - private var negFiles: Option[FileSet] = None - private var runFiles: Option[FileSet] = None - private var jvmFiles: Option[FileSet] = None - private var residentFiles: Option[FileSet] = None - private var buildManagerFiles: Option[FileSet] = None - private var scalacheckFiles: Option[FileSet] = None - private var scriptFiles: Option[FileSet] = None - private var shootoutFiles: Option[FileSet] = None - private var scalapFiles: Option[FileSet] = None - private var errorOnFailed: Boolean = false - private var scalacOpts: Option[String] = None - private var timeout: Option[String] = None - private var jUnitReportDir: Option[File] = None - private var debug = false - - def fileSetToDir(fs: FileSet) = Directory(fs getDir getProject) - def fileSetToArray(fs: FileSet): Array[io.Path] = { - val root = fileSetToDir(fs) - (fs getDirectoryScanner getProject).getIncludedFiles map (root / _) - } - - private def getFiles(fileSet: Option[FileSet]): Array[File] = fileSet match { - case None => Array() - case Some(fs) => fileSetToArray(fs) filterNot (_ hasExtension "log") map (_.jfile) - } - - private def getFilesAndDirs(fileSet: Option[FileSet]): Array[File] = fileSet match { - case None => Array() - case Some(fs) => - def shouldExclude(name: String) = (name endsWith ".obj") || (name startsWith ".") - - val fileTests = getFiles(Some(fs)) filterNot (x => shouldExclude(x.getName)) - val dirTests: Iterator[io.Path] = fileSetToDir(fs).dirs filterNot (x => shouldExclude(x.name)) - val dirResult = dirTests.toList.toArray map (_.jfile) - - dirResult ++ fileTests - } - - 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 getBuildManagerFiles = getFilesAndDirs(buildManagerFiles) - private def getScalacheckFiles = getFiles(scalacheckFiles) - private def getScriptFiles = getFiles(scriptFiles) - private def getShootoutFiles = getFiles(shootoutFiles) - private def getScalapFiles = getFiles(scalapFiles) - - override def execute() { - if (isPartestDebug) - setProp("partest.debug", "true") - - srcDir foreach (x => setProp("partest.srcdir", x)) - - val classpath = this.compilationPath getOrElse error("Mandatory attribute 'compilationPath' is not set.") - - val scalaLibrary = { - (classpath.list map { fs => new File(fs) }) find { f => - f.getName match { - case "scala-library.jar" => true - case "library" if (f.getParentFile.getName == "classes") => true - case _ => false - } - } - } getOrElse error("Provided classpath does not contain a Scala library.") - - val antRunner = new scala.tools.partest.nest.AntRunner - val antFileManager = antRunner.fileManager - - antFileManager.showDiff = showDiff - antFileManager.showLog = showLog - antFileManager.failed = runFailed - antFileManager.CLASSPATH = ClassPath.join(classpath.list: _*) - antFileManager.LATEST_LIB = scalaLibrary.getAbsolutePath - - javacmd foreach (x => antFileManager.JAVACMD = x.getAbsolutePath) - javaccmd foreach (x => antFileManager.JAVAC_CMD = x.getAbsolutePath) - scalacOpts foreach (antFileManager.SCALAC_OPTS = _) - timeout foreach (antFileManager.timeout = _) - - type TFSet = (Array[File], String, String) - val testFileSets = List( - (getPosFiles, "pos", "Compiling files that are expected to build"), - (getNegFiles, "neg", "Compiling files that are expected to fail"), - (getRunFiles, "run", "Compiling and running files"), - (getJvmFiles, "jvm", "Compiling and running files"), - (getResidentFiles, "res", "Running resident compiler scenarii"), - (getBuildManagerFiles, "buildmanager", "Running Build Manager scenarii"), - (getScalacheckFiles, "scalacheck", "Running scalacheck tests"), - (getScriptFiles, "script", "Running script files"), - (getShootoutFiles, "shootout", "Running shootout tests"), - (getScalapFiles, "scalap", "Running scalap tests") - ) - - def runSet(set: TFSet): (Int, Int, Iterable[String]) = { - val (files, name, msg) = set - if (files.isEmpty) (0, 0, List()) - else { - log(msg) - val results: Iterable[(String, Int)] = antRunner.reflectiveRunTestsForFiles(files, name) - val (succs, fails) = resultsToStatistics(results) - - val failed: Iterable[String] = results collect { - case (path, 1) => path + " [FAILED]" - case (path, 2) => path + " [TIMOUT]" - } - - // create JUnit Report xml files if directory was specified - jUnitReportDir foreach { d => - d.mkdir - - val report = testReport(name, results, succs, fails) - scala.xml.XML.save(d.getAbsolutePath+"/"+name+".xml", report) - } - - (succs, fails, failed) - } - } - - val _results = testFileSets map runSet - val allSuccesses = _results map (_._1) sum - val allFailures = _results map (_._2) sum - val allFailedPaths = _results flatMap (_._3) - - 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:\n".format(allFailures, s)+ - allFailedPaths.mkString("\n") - else if (allSuccesses == 0) "There were no tests to run." - else "Test suite finished with no failures." - - f(msg) - } - def oneResult(res: (String, Int)) = - <testcase name={res._1}>{ - 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[(String, Int)], succs: Int, fails: Int) = - <testsuite name={kind} tests={(succs + fails).toString} failures={fails.toString}> - <properties/> - { - results.map(oneResult(_)) - } - </testsuite> -} 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..a830652d5f --- /dev/null +++ b/src/partest/scala/tools/partest/Results.scala @@ -0,0 +1,114 @@ +/* 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..568a155281 --- /dev/null +++ b/src/partest/scala/tools/partest/Universe.scala @@ -0,0 +1,100 @@ +/* __ *\ +** ________ ___ / / ___ 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 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..37e08fee1b --- /dev/null +++ b/src/partest/scala/tools/partest/ant/JavaTask.scala @@ -0,0 +1,52 @@ +/* __ *\ +** ________ ___ / / ___ 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) + getProject.setSystemProperties() + setClassname(scalaRunnerClass) + // setDir(Path(rootDir).jfile) + // addSyspropertyset(partestPropSet) + addSysproperty(newKeyValue("partest.is-in-ant", "true")) + jvmline(partestJVMArgs) + runnerArgs foreach addArg + } + + override def execute() { + setDefaults() + super.execute() + } +} 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..2d67ac5ae4 --- /dev/null +++ b/src/partest/scala/tools/partest/io/Logging.scala @@ -0,0 +1,128 @@ +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 + + /** 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 cb819720fc..0000000000 --- a/src/partest/scala/tools/partest/nest/AntRunner.scala +++ /dev/null @@ -1,30 +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 -import scala.tools.nsc.io.{ Directory } - -class AntRunner extends DirectRunner { - - val fileManager = new FileManager { - var JAVACMD: String = "java" - var JAVAC_CMD: String = "javac" - var CLASSPATH: String = _ - var LATEST_LIB: String = _ - val testRootPath: String = "test" - val testRootDir: Directory = Directory(testRootPath) - } - - def reflectiveRunTestsForFiles(kindFiles: Array[File], kind: String) = - runTestsForFiles(kindFiles.toList, kind) -} diff --git a/src/partest/scala/tools/partest/nest/CompileManager.scala b/src/partest/scala/tools/partest/nest/CompileManager.scala deleted file mode 100644 index 22568ad2d0..0000000000 --- a/src/partest/scala/tools/partest/nest/CompileManager.scala +++ /dev/null @@ -1,197 +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, io } -import scala.tools.nsc.reporters.{ Reporter, ConsoleReporter } -import scala.tools.nsc.util.ClassPath -import scala.tools.util.PathResolver -import io.Path - -import java.io.{ File, BufferedReader, PrintWriter, FileReader, Writer, FileWriter, StringWriter } -import File.pathSeparator - -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"))) -} - -abstract class SimpleCompiler { - def compile(out: Option[File], files: List[File], kind: String, log: File): Boolean -} - -class TestSettings(fileMan: FileManager) extends Settings(_ => ()) { } - -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 = newReporter(settings, logWriter) - rep.shortname = true - newGlobal(settings, rep) - } - - def newSettings(out: Option[String]) = { - val settings = new TestSettings(fileManager) - settings.usejavacp.value = true - settings.deprecation.value = true - settings.nowarnings.value = false - settings.encoding.value = "ISO-8859-1" // XXX why? - - val classpathElements = settings.classpath.value :: fileManager.LATEST_LIB :: out.toList - settings.classpath.value = ClassPath.join(classpathElements: _*) - out foreach (settings.outdir.value = _) - - settings - } - - def newReporter(sett: Settings, writer: Writer = new StringWriter) = - new ExtConsoleReporter(sett, Console.in, new PrintWriter(writer)) - - private def updatePluginPath(options: String): String = { - val dir = fileManager.testRootDir - def absolutize(path: String) = Path(path) match { - case x if x.isAbsolute => x.path - case x => (fileManager.testRootDir / x).toAbsolute.path - } - - val (opt1, opt2) = (options split "\\s").toList partition (_ startsWith "-Xplugin:") - val plugins = opt1 map (_ stripPrefix "-Xplugin:") flatMap (_ split pathSeparator) map absolutize - val pluginOption = if (opt1.isEmpty) Nil else List("-Xplugin:" + (plugins mkString pathSeparator)) - - (opt2 ::: pluginOption) mkString " " - } - - def compile(out: Option[File], files: List[File], kind: String, log: File): Boolean = { - val testSettings = newSettings(out map (_.getAbsolutePath)) - val logWriter = new FileWriter(log) - - // check whether there is a ".flags" file - val flagsFileName = "%s.flags" format (basename(log.getName) dropRight 4) // 4 is "-run" or similar - val argString = (io.File(log).parent / flagsFileName) ifFile (x => updatePluginPath(x.slurp())) getOrElse "" - val allOpts = fileManager.SCALAC_OPTS+" "+argString - val args = (allOpts split "\\s").toList - - NestUI.verbose("scalac options: "+allOpts) - - val command = new CompilerCommand(args, testSettings) - val global = newGlobal(command.settings, logWriter) - val testRep: ExtConsoleReporter = global.reporter.asInstanceOf[ExtConsoleReporter] - - val testFileFn: (File, FileManager) => TestFile = kind match { - case "pos" => PosTestFile.apply - case "neg" => NegTestFile.apply - case "run" => RunTestFile.apply - case "jvm" => JvmTestFile.apply - case "shootout" => ShootoutTestFile.apply - case "scalap" => ScalapTestFile.apply - case "scalacheck" => ScalaCheckTestFile.apply - } - val test: TestFile = testFileFn(files.head, fileManager) - test.defineSettings(command.settings, out.isEmpty) - 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 58d16a3f45..0000000000 --- a/src/partest/scala/tools/partest/nest/ConsoleFileManager.scala +++ /dev/null @@ -1,190 +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.util.Properties.{ propOrElse, scalaCmd, scalacCmd } -import scala.tools.util.PathResolver -import scala.tools.nsc.{ Settings } -import scala.tools.nsc.{ io, util } -import util.{ ClassPath } -import io.{ Path, Directory } -import File.pathSeparator -import ClassPath.{ join } -import PathResolver.{ Environment, Defaults } -import RunnerUtils._ - - -class ConsoleFileManager extends FileManager { - var testBuild: Option[String] = PartestDefaults.testBuild - def testBuildFile = testBuild map (testParent / _) - - var testClasses: Option[String] = None - - def this(buildPath: String, rawClasses: Boolean) = { - this() - if (rawClasses) - testClasses = Some(buildPath) - else - testBuild = Some(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 - } - - lazy val srcDir = PathSettings.srcDir - lazy val testRootDir = PathSettings.testRoot - lazy val testRootPath = testRootDir.toAbsolute.path - def testParent = testRootDir.parent - - var CLASSPATH = PartestDefaults.classPath - var JAVACMD = PartestDefaults.javaCmd - var JAVAC_CMD = PartestDefaults.javacCmd - - - NestUI.verbose("CLASSPATH: "+CLASSPATH) - - if (!srcDir.isDirectory) { - NestUI.failure("Source directory \"" + srcDir.path + "\" not found") - exit(1) - } - - CLASSPATH = { - val libs = (srcDir / Directory("lib")).files filter (_ hasExtension "jar") map (_.normalize.path) - - // add all jars in libs - (CLASSPATH :: libs.toList) mkString pathSeparator - } - - def findLatest() { - NestUI.verbose("test parent: "+testParent) - - def prefixFileWith(parent: File, relPath: String) = (io.File(parent) / relPath).normalize - def prefixFile(relPath: String) = (testParent / relPath).normalize - - if (!testClasses.isEmpty) { - testClassesDir = Path(testClasses.get).normalize.toDirectory - NestUI.verbose("Running with classes in "+testClassesDir) - - latestFile = testClassesDir.parent / "bin" - latestLibFile = testClassesDir / "library" - latestCompFile = testClassesDir / "compiler" - latestPartestFile = testClassesDir / "partest" - latestFjbgFile = testParent / "lib" / "fjbg.jar" - } - else if (testBuild.isDefined) { - val dir = Path(testBuild.get) - NestUI.verbose("Running on "+dir) - latestFile = dir / "bin" - latestLibFile = dir / "lib/scala-library.jar" - latestCompFile = dir / "lib/scala-compiler.jar" - latestPartestFile = dir / "lib/scala-partest.jar" - } - else { - def setupQuick() { - NestUI.verbose("Running build/quick") - latestFile = prefixFile("build/quick/bin") - latestLibFile = 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") - 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") - 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") - latestCompFile = prefixFile("build/pack/lib/scala-compiler.jar") - latestPartestFile = prefixFile("build/pack/lib/scala-partest.jar") - } - - val dists = testParent / "dists" - val build = testParent / "build" - // in case of an installed dist, testRootDir is one level deeper - val bin = testParent.parent / "bin" - - def mostRecentOf(base: String, names: String*) = - names map (x => prefixFile(base + "/" + x).lastModified) reduceLeft (_ max _) - - // detect most recent build - val quickTime = mostRecentOf("build/quick/classes", "compiler/compiler.properties", "library/library.properties") - val packTime = mostRecentOf("build/pack/lib", "scala-compiler.jar", "scala-library.jar") - val distTime = mostRecentOf("dists/latest/lib", "scala-compiler.jar", "scala-library.jar") - val instTime = mostRecentOf("lib", "scala-compiler.jar", "scala-library.jar") - - val pairs = Map( - (quickTime, () => setupQuick()), - (packTime, () => setupPack()), - (distTime, () => setupDist()), - (instTime, () => setupInst()) - ) - - // run setup based on most recent time - pairs(pairs.keys max)() - - latestFjbgFile = prefixFile("lib/fjbg.jar") - } - - LATEST_LIB = latestLibFile.getAbsolutePath - } - - var LATEST_LIB: String = "" - - var latestFile: File = _ - var latestLibFile: File = _ - var latestCompFile: File = _ - var latestPartestFile: File = _ - var latestFjbgFile: File = _ - var testClassesDir: Directory = _ - // initialize above fields - findLatest() - - var testFiles: List[io.Path] = Nil - - def getFiles(kind: String, cond: Path => Boolean): List[File] = { - def ignoreDir(p: Path) = List("svn", "obj") exists (p hasExtension _) - - val dir = Directory(srcDir / kind) - - if (dir.isDirectory) NestUI.verbose("look in %s for tests" format dir) - else NestUI.failure("Directory '%s' not found" format dir) - - val files = - if (testFiles.nonEmpty) testFiles filter (_.parent isSame dir) - else dir.list filterNot ignoreDir filter cond toList - - ( if (failed) files filter (x => logFileExists(x, kind)) else files ) map (_.jfile) - } -} 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 eae79f23af..0000000000 --- a/src/partest/scala/tools/partest/nest/ConsoleRunner.scala +++ /dev/null @@ -1,209 +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._ -import RunnerUtils._ -import scala.tools.nsc.Properties.{ versionMsg, setProp } -import scala.tools.nsc.util.CommandLineParser -import scala.tools.nsc.io -import scala.tools.nsc.interpreter.returning -import io.{ Path, Process } - -class ConsoleRunner extends DirectRunner { - import PathSettings.{ srcDir, testRoot } - - case class TestSet(kind: String, filter: Path => Boolean, msg: String) - - val testSets = { - val pathFilter: Path => Boolean = _ hasExtension "scala" - - List( - TestSet("pos", pathFilter, "Testing compiler (on files whose compilation should succeed)"), - TestSet("neg", pathFilter, "Testing compiler (on files whose compilation should fail)"), - TestSet("run", pathFilter, "Testing JVM backend"), - TestSet("jvm", pathFilter, "Testing JVM backend"), - TestSet("res", x => x.isFile && (x hasExtension "res"), "Testing resident compiler"), - TestSet("buildmanager", _.isDirectory, "Testing Build Manager"), - TestSet("shootout", pathFilter, "Testing shootout tests"), - TestSet("script", pathFilter, "Testing script tests"), - TestSet("scalacheck", pathFilter, "Testing ScalaCheck tests"), - TestSet("scalap", _.isDirectory, "Run scalap decompiler tests") - ) - } - - var fileManager: ConsoleFileManager = _ - - private var testFiles: List[File] = List() - private val errors = PartestDefaults.errorCount - private val testSetKinds = testSets map (_.kind) - private val testSetArgs = testSets map ("--" + _.kind) - private val testSetArgMap = testSetArgs zip testSets toMap - - def denotesTestSet(arg: String) = testSetArgs contains arg - def denotesTestFile(arg: String) = (arg endsWith ".scala") || (arg endsWith ".res") - def denotesTestDir(arg: String) = Path(arg).isDirectory - def denotesTestPath(arg: String) = denotesTestDir(arg) || denotesTestFile(arg) - - private def printVersion { NestUI outline (versionMsg + "\n") } - - private val unaryArgs = List( - "--pack", "--all", "--verbose", "--show-diff", "--show-log", - "--failed", "--version", "--ansi", "--debug" - ) ::: testSetArgs - - private val binaryArgs = List( - "--grep", "--srcpath", "--buildpath", "--classpath" - ) - - def main(argstr: String) { - val parsed = CommandLineParser(argstr) withUnaryArgs unaryArgs withBinaryArgs binaryArgs - val args = parsed.residualArgs - - /** Early return on no args, version, or invalid args */ - if (argstr == "") return NestUI.usage() - if (parsed isSet "--version") return printVersion - if (args exists (x => !denotesTestPath(x))) { - val invalid = (args filterNot denotesTestPath).head - NestUI.failure("Invalid argument '%s'\n" format invalid) - return NestUI.usage() - } - - parsed get "--srcpath" foreach (x => setProp("partest.srcdir", x)) - - fileManager = - if (parsed isSet "--buildpath") new ConsoleFileManager(parsed("--buildpath")) - else if (parsed isSet "--classpath") new ConsoleFileManager(parsed("--classpath"), true) - else if (parsed isSet "--pack") new ConsoleFileManager("build/pack") - else new ConsoleFileManager // auto detection, see ConsoleFileManager.findLatest - - def argNarrowsTests(x: String) = denotesTestSet(x) || denotesTestFile(x) || denotesTestDir(x) - - NestUI._verbose = parsed isSet "--verbose" - fileManager.showDiff = parsed isSet "--show-diff" - fileManager.showLog = parsed isSet "--show-log" - fileManager.failed = parsed isSet "--failed" - - if (parsed isSet "--ansi") NestUI initialize NestUI.MANY - if (parsed isSet "--timeout") fileManager.timeout = parsed("--timeout") - if (parsed isSet "--debug") setProp("partest.debug", "true") - - def addTestFile(file: File) = { - if (!file.exists) - NestUI.failure("Test file '%s' not found, skipping.\n" format file) - else { - NestUI.verbose("adding test file " + file) - testFiles +:= file - } - } - - // If --grep is given we suck in every file it matches. - parsed get "--grep" foreach { expr => - val allFiles = srcDir.deepList() filter (_ hasExtension "scala") map (_.toFile) toList - val files = allFiles filter (_.slurp() contains expr) - - if (files.isEmpty) NestUI.failure("--grep string '%s' matched no files." format expr) - else NestUI.verbose("--grep string '%s' matched %d file(s)".format(expr, files.size)) - - files foreach (x => addTestFile(x.jfile)) - } - args foreach (x => addTestFile(new File(x))) - - // If no file arguments were given, we assume --all - val enabledTestSets: List[TestSet] = { - val enabledArgs = testSetArgs filter parsed.isSet - - if (args.isEmpty && !(parsed isSet "--grep") && (enabledArgs.isEmpty || (parsed isSet "--all"))) testSets - else enabledArgs map testSetArgMap - } - - val dir = - if (fileManager.testClasses.isDefined) fileManager.testClassesDir - else fileManager.testBuildFile getOrElse { - fileManager.latestCompFile.getParentFile.getParentFile.getCanonicalFile - } - - val vmBin = javaHome + File.separator + "bin" - val vmName = "%s (build %s, %s)".format(javaVmName, javaVmVersion, javaVmInfo) - val vmOpts = fileManager.JAVA_OPTS - - NestUI.verbose("enabled test sets: " + (enabledTestSets map (_.kind) mkString " ")) - - List( - "Scala compiler classes in: " + dir, - "Scala version is: " + versionMsg, - "Scalac options are: " + fileManager.SCALAC_OPTS, - "Java binaries in: " + vmBin, - "Java runtime is: " + vmName, - "Java options are: " + vmOpts, - "Source directory is: " + srcDir, - "" - ) foreach (x => NestUI outline (x + "\n")) - - val start = System.currentTimeMillis - val (successes, failures) = testCheckAll(enabledTestSets) - 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") - - System exit ( if (failures == errors) 0 else 1 ) - } - - def runTests(testSet: TestSet): (Int, Int) = { - val TestSet(kind, filter, msg) = testSet - - fileManager.getFiles(kind, filter) match { - case Nil => NestUI.verbose("test dir empty\n") ; (0, 0) - case files => - NestUI.verbose("test files: "+files) - NestUI.outline("\n"+msg+"\n") - resultsToStatistics(runTestsForFiles(files, kind)) - } - } - - /** - * @return (success count, failure count) - */ - def testCheckAll(enabledSets: List[TestSet]): (Int, Int) = { - def kindOf(f: File) = (srcDir relativize Path(f).normalize).segments.head - - val (valid, invalid) = testFiles partition (x => testSetKinds contains kindOf(x)) - invalid foreach (x => NestUI.failure("Invalid test file '%s', skipping.\n" format x)) - - val runTestsFileLists = - for ((kind, files) <- valid groupBy kindOf toList) yield { - NestUI.outline("\nTesting individual files\n") - resultsToStatistics(runTestsForFiles(files, kind)) - } - - NestUI.verbose("Run sets: "+enabledSets) - val results = runTestsFileLists ::: (enabledSets map runTests) - - (results map (_._1) sum, results map (_._2) sum) - } -} 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 f774320f4e..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.util.Properties.{ setProp } -import scala.tools.nsc.io.Directory - -import scala.actors.Actor._ -import scala.actors.TIMEOUT - -trait DirectRunner { - - def fileManager: FileManager - - import PartestDefaults.numActors - - if (isPartestDebug) - scala.actors.Debug.level = 3 - - if (PartestDefaults.poolSize.isEmpty) { - scala.actors.Debug.info("actors.corePoolSize not defined") - setProp("actors.corePoolSize", "16") - } - - def runTestsForFiles(kindFiles: List[File], kind: String): scala.collection.immutable.Map[String, 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 logsToDelete: List[File] = List() - var outdirsToDelete: List[File] = List() - var results = new scala.collection.immutable.HashMap[String, Int] - workers foreach { w => - receiveWithin(3600 * 1000) { - case Results(res, logs, outdirs) => - logsToDelete :::= logs filter (_.toDelete) - outdirsToDelete :::= outdirs - results ++= res - case TIMEOUT => - // add at least one failure - NestUI.verbose("worker timed out; adding failed test") - results += ("worker timed out; adding failed test" -> 2) - } - } - - if (isPartestDebug) - fileManager.showTestTimings() - - if (!isPartestDebug) { - for (x <- logsToDelete ::: outdirsToDelete) { - NestUI.verbose("deleting "+x) - Directory(x).deleteRecursively() - } - } - - results - } - -} 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 bdbb34b3c4..0000000000 --- a/src/partest/scala/tools/partest/nest/FileManager.scala +++ /dev/null @@ -1,110 +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, - FileInputStream, FileOutputStream, BufferedReader, - FileReader, PrintWriter, FileWriter} -import java.net.URI -import scala.tools.nsc.io.{ Path, Directory } -import scala.collection.mutable.HashMap - -trait FileManager { - /** - * 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 = { - val diffWriter = new StringWriter - val args = Array(f1.getCanonicalPath(), f2.getCanonicalPath()) - - DiffPrint.doDiff(args, diffWriter) - val res = diffWriter.toString - if (res startsWith "No") "" else res - } - - def testRootDir: Directory - def testRootPath: String - - var JAVACMD: String - var JAVAC_CMD: String - - var CLASSPATH: String - var LATEST_LIB: String - - var showDiff = false - var showLog = false - var failed = false - - var SCALAC_OPTS = PartestDefaults.scalacOpts - var JAVA_OPTS = PartestDefaults.javaOpts - var timeout = PartestDefaults.timeout - - /** Only when --debug is given. */ - lazy val testTimings = new HashMap[String, Long] - def recordTestTiming(name: String, milliseconds: Long) = - synchronized { testTimings(name) = milliseconds } - def showTestTimings() { - testTimings.toList sortBy (-_._2) foreach { case (k, v) => println("%s: %s".format(k, v)) } - } - - def getLogFile(dir: File, fileBase: String, kind: String): 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) = - getLogFile(file, kind).canRead - - def overwriteFileWith(dest: File, file: File) = - dest.isFile && copyFile(file, dest) - - - def copyFile(from: File, dest: File): Boolean = { - def copyFile0(from: File, to: File): Boolean = - try { - val appender = StreamAppender(from, to) - appender.run() - appender.closeAll() - true - } catch { - case _: IOException => false - } - - if (from.isDirectory) { - assert(dest.isDirectory, "cannot copy directory to file") - val subDir:Directory = Path(dest) / Directory(from.getName) - subDir.createDirectory() - from.listFiles.toList.forall(copyFile(_, subDir)) - } else - copyFile0(from, if (dest.isDirectory) new File(dest, from.getName) else dest) - } - - def mapFile(file: File, suffix: String, dir: File, replace: String => String) { - val tmpFile = File.createTempFile("tmp", suffix, dir) // prefix required by API - - val appender = StreamAppender(file, tmpFile) - appender.runAndMap(replace) - appender.closeAll() - - val appender2 = StreamAppender(tmpFile, file) - appender2.run() - appender2.closeAll() - - tmpFile.delete() - } -} 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 efff4e8375..0000000000 --- a/src/partest/scala/tools/partest/nest/NestUI.scala +++ /dev/null @@ -1,118 +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(" --buildmanager run Build Manager tests") - println(" --scalacheck run ScalaCheck tests") - println(" --script run script runner tests") - println(" --shootout run shootout tests") - println(" --grep <expr> run all tests whose source file contains <expr>") - 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(" --debug enable debugging output") - println - println(utils.Properties.versionString) - println("maintained by Philipp Haller (EPFL)") - exit(1) - } - - var _verbose = false - var _debug = false - - def verbose(msg: String) { - if (_verbose) { - outline("debug: ") - println(msg) - } - } - def debug(msg: String) { - if (isPartestDebug) { - outline("debug: ") - println(msg) - } - } -} diff --git a/src/partest/scala/tools/partest/nest/PathSettings.scala b/src/partest/scala/tools/partest/nest/PathSettings.scala deleted file mode 100644 index 41bba5782e..0000000000 --- a/src/partest/scala/tools/partest/nest/PathSettings.scala +++ /dev/null @@ -1,41 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - */ - -package scala.tools.partest -package nest - -import scala.tools.nsc.Properties.{ setProp, propOrEmpty, propOrNone, propOrElse } -import scala.tools.nsc.util.ClassPath -import scala.tools.nsc.io -import io.{ Path, File, Directory } -import RunnerUtils._ -import java.net.URLClassLoader - -object PathSettings { - import PartestDefaults.{ testRootDir, srcDirName } - - private def cwd = Directory.Current getOrElse error("user.dir property not set") - private def isPartestDir(d: Directory) = (d.name == "test") && (d / srcDirName isDirectory) - - // Directory <root>/test - lazy val testRoot: Directory = testRootDir getOrElse { - val candidates: List[Directory] = (cwd :: cwd.parents) flatMap (d => List(d, Directory(d / "test"))) - - candidates find isPartestDir getOrElse error("Directory 'test' not found.") - } - - // Directory <root>/test/files - lazy val srcDir = Directory(testRoot / srcDirName normalize) - - // Directory <root>/test/files/lib - lazy val srcLibDir = Directory(srcDir / "lib") - - lazy val scalaCheck = srcLibDir.files find (_.name startsWith "scalacheck") getOrElse { - error("No scalacheck jar found in '%s'" format srcLibDir) - } -} - -class PathSettings() { - // def classpathAsURLs: List[URL] -} 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 b3f199a3d6..0000000000 --- a/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala +++ /dev/null @@ -1,88 +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.Properties.{ setProp, propOrEmpty } -import scala.tools.nsc.util.ClassPath -import scala.tools.nsc.io -import io.Path -import RunnerUtils._ -import java.net.URLClassLoader - -/* This class is used to load an instance of DirectRunner using - * 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 { - // TODO: we might also use fileManager.CLASSPATH - // to use the same classes as used by `scala` that - // was used to start the runner. - val sepRunnerClassName = "scala.tools.partest.nest.ConsoleRunner" - - def main(args: String) { - val argList = (args.split("\\s")).toList - - if (isPartestDebug) - showAllJVMInfo - - // 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, latestPartestFile, latestFjbgFile } - val files = - Array(latestCompFile, latestLibFile, latestPartestFile, latestFjbgFile) map (x => io.File(x)) - - val sepUrls = files map (_.toURL) - val sepLoader = new URLClassLoader(sepUrls, null) - - if (isPartestDebug) - println("Loading classes from:\n" + sepUrls.mkString("\n")) - - val paths = classPath match { - case Some(cp) => Nil - case _ => files.toList map (_.path) - } - val newClasspath = ClassPath.join(paths: _*) - - setProp("java.class.path", newClasspath) - setProp("scala.home", "") - - if (isPartestDebug) - for (prop <- List("java.class.path", "sun.boot.class.path", "java.ext.dirs")) - println(prop + ": " + propOrEmpty(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 24445bb545..0000000000 --- a/src/partest/scala/tools/partest/nest/RunnerUtils.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -object RunnerUtils { - def splitArgs(str: String) = str split "\\s" filterNot (_ == "") toList - - def searchPath(option: String, as: List[String]): Option[String] = as match { - case `option` :: r :: _ => Some(r) - case _ :: rest => searchPath(option, rest) - case Nil => None - } - - def searchAndRemovePath(option: String, as: List[String]) = (as indexOf option) match { - case -1 => (None, as) - case idx => (Some(as(idx + 1)), (as take idx) ::: (as drop (idx + 2))) - } - - def searchAndRemoveOption(option: String, as: List[String]) = (as indexOf option) match { - case -1 => (false, as) - case idx => (true, (as take idx) ::: (as drop (idx + 1))) - } -} diff --git a/src/partest/scala/tools/partest/nest/StreamAppender.scala b/src/partest/scala/tools/partest/nest/StreamAppender.scala deleted file mode 100644 index 8cebcf1685..0000000000 --- a/src/partest/scala/tools/partest/nest/StreamAppender.scala +++ /dev/null @@ -1,94 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Philipp Haller - */ - -// $Id$ - -package scala.tools.partest -package nest - -import java.io._ - -object StreamAppender { - def wrapIn(in: InputStream): BufferedReader = new BufferedReader(new InputStreamReader(in)) - def wrapIn(reader: Reader): BufferedReader = new BufferedReader(reader) - def wrapIn(str: String): BufferedReader = new BufferedReader(new StringReader(str)) - - def wrapOut(out: OutputStream): PrintWriter = new PrintWriter(new OutputStreamWriter(out), true) - def wrapOut(writer: Writer): PrintWriter = new PrintWriter(writer, true) - def wrapOut(): PrintWriter = wrapOut(new StringWriter) - - def apply(reader: BufferedReader, writer: Writer): StreamAppender = - new StreamAppender(reader, wrapOut(writer)) - - def apply(reader: Reader, writer: Writer): StreamAppender = - apply(wrapIn(reader), writer) - - def apply(in: InputStream, writer: Writer): StreamAppender = - apply(wrapIn(in), writer) - - def apply(str: String, writer: Writer): StreamAppender = - apply(wrapIn(str), writer) - - def apply(in: File, out: File): StreamAppender = - apply(new FileReader(in), new FileWriter(out)) - - def appendToString(in1: InputStream, in2: InputStream): String = { - val swriter1 = new StringWriter - val swriter2 = new StringWriter - val app1 = StreamAppender(wrapIn(in1), swriter1) - val app2 = StreamAppender(wrapIn(in2), 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 = wrapOut(out) - val inApp = StreamAppender(in, outWriter) - - val errStringWriter = new StringWriter - val errApp = StreamAppender(wrapIn(err), errStringWriter) - - inParallel(inApp, errApp) - - // append error string to out - StreamAppender(errStringWriter.toString, outWriter).run() - } - } -} - -class StreamAppender(reader: BufferedReader, writer: PrintWriter) extends Runnable { - override def run() = runAndMap(identity) - private def lines() = Iterator continually reader.readLine() takeWhile (_ != null) - def closeAll() = { - reader.close() - writer.close() - } - - def runAndMap(f: String => String) = - try lines() map f foreach (writer println _) - 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 741556fdd5..0000000000 --- a/src/partest/scala/tools/partest/nest/TestFile.scala +++ /dev/null @@ -1,49 +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 => JFile } -import scala.tools.nsc.Settings -import scala.tools.nsc.io._ - -abstract class TestFile(kind: String) { - def file: JFile - def fileManager: FileManager - - val dir = file.toAbsolute.parent - val fileBase = file.stripExtension - lazy val objectDir = dir / "%s-%s.obj".format(fileBase, kind) createDirectory true - val flags: Option[String] = dir / "%s.flags".format(fileBase) ifFile { _.slurp().trim } - - def setOutDirTo = objectDir - - def defineSettings(settings: Settings, setOutDir: Boolean) = { - settings.classpath append dir.path - if (setOutDir) - settings.outdir.value = setOutDirTo.path - - flags foreach (settings processArgumentString _) - settings.classpath append fileManager.CLASSPATH - } - - override def toString(): String = "%s %s".format(kind, file) -} - -case class PosTestFile(file: JFile, fileManager: FileManager) extends TestFile("pos") -case class NegTestFile(file: JFile, fileManager: FileManager) extends TestFile("neg") -case class RunTestFile(file: JFile, fileManager: FileManager) extends TestFile("run") -case class BuildManagerTestFile(file: JFile, fileManager: FileManager) extends TestFile("bm") -case class ScalaCheckTestFile(file: JFile, fileManager: FileManager) extends TestFile("scalacheck") -case class JvmTestFile(file: JFile, fileManager: FileManager) extends TestFile("jvm") -case class ShootoutTestFile(file: JFile, fileManager: FileManager) extends TestFile("shootout") { - override def setOutDirTo = file.parent -} -case class ScalapTestFile(file: JFile, fileManager: FileManager) extends TestFile("scalap") { - override def setOutDirTo = file.parent -} 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 2f81dfd0f7..0000000000 --- a/src/partest/scala/tools/partest/nest/Worker.scala +++ /dev/null @@ -1,1071 +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.util.Properties.{ isWin } -import scala.tools.nsc.{ ObjectRunner, Settings, CompilerCommand, Global } -import scala.tools.nsc.io.{ AbstractFile, PlainFile, Path, Directory, File => SFile } -import scala.tools.nsc.reporters.ConsoleReporter -import scala.tools.nsc.util.{ ClassPath, FakePos } -import ClassPath.{ join, split } - -import scala.actors.{ Actor, Exit, TIMEOUT } -import scala.actors.Actor._ -import scala.tools.scalap.scalax.rules.scalasig.{ByteCode, ClassFileParser, ScalaSigAttributeParsers} - -import scala.collection.immutable.{ HashMap, Map => ImmMap } -import scala.collection.Map - -import scala.tools.nsc.interactive.{BuildManager, RefinedBuildManager} - -case class RunTests(kind: String, files: List[File]) -case class Results(results: ImmMap[String, 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._ - - var reporter: ConsoleReporter = _ - val timer = new Timer - - def error(msg: String): Unit = 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) { results => - master ! Results(results, createdLogFiles, createdOutputDirs) - } - } - } - - 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("[...]%s%s".format(name, " " * (totalWidth - name.length)), 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] = Nil - var createdOutputDirs: List[File] = Nil - - def createLogFile(file: File, kind: String): LogFile = { - val logFile = fileManager.getLogFile(file, kind) - createdLogFiles ::= logFile - logFile - } - - def createOutputDir(dir: File, fileBase: String, kind: String): File = { - val outDir = Path(dir) / 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 - // - // val classpath: List[URL] = { - // import consFM.{ latestCompFile, latestLibFile, latestPartestFile } - // val units = ( - // List(outDir, latestCompFile, latestLibFile, latestPartestFile) ::: - // ((CLASSPATH split File.pathSeparatorChar).toList map (x => new File(x))) - // ) - // units map (_.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 { - // withOutputRedirected(logWriter) { - // System.setProperty("java.library.path", logFile.getParentFile.getCanonicalFile.getAbsolutePath) - // System.setProperty("partest.output", outDir.getCanonicalFile.getAbsolutePath) - // System.setProperty("partest.lib", LATEST_LIB) - // System.setProperty("partest.cwd", outDir.getParent) - // ObjectRunner.run(classpath, "Test", List("jvm")) - // } - // } - // - // /*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 "+ join(outDir.toString, 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 = StreamAppender(in, writer) - val errApp = StreamAppender(err, writer) - val async = new Thread(errApp) - async.start() - inApp.run() - async.join() - writer.close() - - try proc.exitValue() - catch { case _: 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 "" - - 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 - // partest.java_opts, leading to debug output like: - // - // debug: Found javaopts file 'files/shootout/message.scala-2.javaopts', using options: '-Xss32k' - // debug: java -Xss32k -Xss2m -Xms256M -Xmx1024M -classpath [...] - val propertyOptions = List( - "-Djava.library.path="+logFile.getParentFile.getAbsolutePath, - "-Dpartest.output="+outDir.getAbsolutePath, - "-Dpartest.lib="+LATEST_LIB, - "-Dpartest.cwd="+outDir.getParent, - "-Djavacmd="+JAVACMD, - "-Duser.language=en -Duser.country=US" - ) ::: ( - if (isPartestDebug) List("-Dpartest.debug=true") else Nil - ) - - val cmd = ( - List( - JAVACMD, - JAVA_OPTS, - argString, - "-classpath " + join(outDir.toString, CLASSPATH) - ) ::: propertyOptions ::: List( - "scala.tools.nsc.MainGenericRunner", - "-usejavacp", - "Test", - "jvm" - ) - ) mkString " " - - 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 getCheckFile(dir: File, fileBase: String, kind: String) = { - def chkFile(s: String) = Directory(dir) / "%s%s.check".format(fileBase, s) - val checkFile = if (chkFile("").isFile) chkFile("") else chkFile("-" + kind) - - if (checkFile.canRead) Some(checkFile) else None - } - - def existsCheckFile(dir: File, fileBase: String, kind: String) = - getCheckFile(dir, fileBase, kind).isDefined - - def compareOutput(dir: File, fileBase: String, kind: String, logFile: File): String = - // if check file exists, compare with log file - getCheckFile(dir, fileBase, kind) match { - case Some(f) => fileManager.compareFiles(logFile, f.jfile) - case _ => file2String(logFile) - } - - def file2String(logFile: File) = SFile(logFile).slurp() - def isJava(f: File) = SFile(f) hasExtension "java" - def isScala(f: File) = SFile(f) hasExtension "scala" - def isJavaOrScala(f: File) = isJava(f) || isScala(f) - - /** 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: ImmMap[String, Int] => Unit) { - val compileMgr = new CompileManager(fileManager) - var errors = 0 - var succeeded = true - var diff = "" - var log = "" - - def fail(what: Any) { - NestUI.verbose("scalac: compilation of "+what+" failed\n") - succeeded = false - } - def diffCheck(latestDiff: String) = { - diff = latestDiff - if (latestDiff != "") { - NestUI.verbose("output differs from log file\n") - succeeded = false - } - } - - /** 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.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 { - if (isPartestDebug) { - val t1 = System.currentTimeMillis - script(logFile, outDir) - val t2 = System.currentTimeMillis - fileManager.recordTestTiming(file.getPath, t2 - t1) - } - else { - 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 filter isJavaOrScala - - def isInGroup(f: File, num: Int) = SFile(f).stripExtension endsWith ("_" + num) - val groups = (0 to 9).toList map (num => testFiles filter (f => isInGroup(f, num))) - val noGroupSuffix = testFiles -- groups.flatten - - def compileGroup(g: List[File]) { - val (scalaFiles, javaFiles) = g partition isScala - - if (scalaFiles.nonEmpty) { - if (!compileMgr.shouldCompile(outDir, javaFiles ::: scalaFiles, kind, logFile)) - fail(g) - } - - if (succeeded && javaFiles.nonEmpty) { - succeeded = javac(outDir, javaFiles, logFile) - if (succeeded && scalaFiles.nonEmpty && !compileMgr.shouldCompile(outDir, scalaFiles, kind, logFile)) - fail(scalaFiles) - } - } - - if (noGroupSuffix.nonEmpty) - compileGroup(noGroupSuffix) - - groups foreach (grp => if (succeeded) compileGroup(grp)) - } - - def failCompileFilesIn(dir: File, kind: String, logFile: File, outDir: File) { - val testFiles = dir.listFiles.toList - val sourceFiles = testFiles filter isJavaOrScala - - if (sourceFiles.nonEmpty) { - if (!compileMgr.shouldFailCompile(outDir, sourceFiles, kind, logFile)) - fail(testFiles filter isScala) - } - } - - def runTestCommon(file: File, kind: String, expectFailure: Boolean)(onSuccess: (File, File) => Unit): LogContext = - runInContext(file, kind, (logFile: File, outDir: File) => { - - if (file.isDirectory) { - val f = if (expectFailure) failCompileFilesIn _ else compileFilesIn _ - f(file, kind, logFile, outDir) - } - else { - val f: (List[File], String, File) => Boolean = - if (expectFailure) compileMgr.shouldFailCompile _ - else compileMgr.shouldCompile _ - - if (!f(List(file), kind, logFile)) - fail(file) - } - - if (succeeded) // run test - onSuccess(logFile, outDir) - }) - - def runJvmTest(file: File, kind: String): LogContext = - runTestCommon(file, kind, expectFailure = false)((logFile, outDir) => { - 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) - execTest(outDir, logFile, fileBase) - - diffCheck(compareOutput(dir, fileBase, kind, logFile)) - }) - - def processSingleFile(file: File): LogContext = kind match { - case "scalacheck" => - runTestCommon(file, kind, expectFailure = false)((logFile, outDir) => { - val consFM = new ConsoleFileManager - import consFM.{ latestCompFile, latestLibFile, latestPartestFile } - - NestUI.verbose("compilation of "+file+" succeeded\n") - - val scalacheckURL = PathSettings.scalaCheck.toURL - val outURL = outDir.getCanonicalFile.toURI.toURL - val classpath: List[URL] = - List(outURL, scalacheckURL, latestCompFile.toURI.toURL, latestLibFile.toURI.toURL, latestPartestFile.toURI.toURL).distinct - - NestUI.debug("scalacheck urls") - classpath foreach (x => NestUI.debug(x.toString)) - - val logWriter = new PrintStream(new FileOutputStream(logFile)) - - withOutputRedirected(logWriter) { - ObjectRunner.run(classpath, "Test", Nil) - } - - NestUI.verbose(SFile(logFile).slurp()) - // obviously this must be improved upon - succeeded = SFile(logFile).lines() forall (_ contains " OK") - }) - - case "pos" => - runTestCommon(file, kind, expectFailure = false)((_, _) => ()) - - case "neg" => - runTestCommon(file, kind, expectFailure = true)((logFile, outDir) => { - // compare log file to check file - val fileBase = basename(file.getName) - val dir = file.getParentFile - - diffCheck( - // diff is contents of logFile - if (!existsCheckFile(dir, fileBase, kind)) file2String(logFile) - else compareOutput(dir, fileBase, kind, logFile) - ) - }) - - case "run" | "jvm" => - runJvmTest(file, kind) - - case "buildmanager" => - val logFile = createLogFile(file, kind) - if (!fileManager.failed || logFile.canRead) { - val swr = new StringWriter - val wr = new PrintWriter(swr) - succeeded = true; diff = "" - printInfoStart(file, wr) - val (outDir, testFile, changesDir, fileBase) = - - if (!file.isDirectory) { - succeeded = false - (null, null, null, null) - } else { - val fileBase: String = basename(file.getName) - NestUI.verbose(this+" running test "+fileBase) - val outDir = createOutputDir(file, fileBase, kind) - if (!outDir.exists) outDir.mkdir() - val testFile = new File(file, fileBase + ".test") - val changesDir = new File(file, fileBase + ".changes") - if (changesDir.isFile || !testFile.isFile) { - // if changes exists then it has to be a dir - if (!testFile.isFile) NestUI.verbose("invalid build manager test file") - if (changesDir.isFile) NestUI.verbose("invalid build manager changes directory") - succeeded = false - (null, null, null, null) - } else { - copyTestFiles(file, outDir) - NestUI.verbose("outDir: "+outDir) - NestUI.verbose("logFile: "+logFile) - (outDir, testFile, changesDir, fileBase) - } - } - - if (succeeded) { - // Pre-conditions satisfied - - try { - val sourcepath = outDir.getAbsolutePath+File.separator - - // configure input/output files - val logWriter = new PrintStream(new FileOutputStream(logFile)) - val testReader = new BufferedReader(new FileReader(testFile)) - val logConsoleWriter = new PrintWriter(logWriter) - - // create proper settings for the compiler - val settings = new Settings(error) - settings.outdir.value = outDir.getCanonicalFile.getAbsolutePath - settings.sourcepath.value = sourcepath - settings.classpath.value = fileManager.CLASSPATH - settings.Ybuildmanagerdebug.value = true - - // simulate Build Manager loop - val prompt = "builder > " - reporter = new ConsoleReporter(settings, scala.Console.in, logConsoleWriter) - val bM: BuildManager = - new RefinedBuildManager(settings) { - override protected def newCompiler(settings: Settings) = - new BuilderGlobal(settings, reporter) - } - - val testCompile = (line: String) => { - NestUI.verbose("compiling " + line) - val args = (line split ' ').toList - val command = new CompilerCommand(args, settings) - bM.update(filesToSet(settings.sourcepath.value, command.files), Set.empty) - !reporter.hasErrors - } - - val updateFiles = (line: String) => { - NestUI.verbose("updating " + line) - val res = - ((line split ' ').toList).forall(u => { - (u split "=>").toList match { - case origFileName::(newFileName::Nil) => - val newFile = new File(changesDir, newFileName) - if (newFile.isFile) { - val v = overwriteFileWith(new File(outDir, origFileName), newFile) - if (!v) - NestUI.verbose("'update' operation on " + u + " failed") - v - } else { - NestUI.verbose("File " + newFile + " is invalid") - false - } - case a => - NestUI.verbose("Other =: " + a) - false - } - }) - if (!res) - NestUI.verbose("updating failed") - else - NestUI.verbose("updating succeeded") - res - } - - def loop() { - val command = testReader.readLine() - if ((command ne null) && command.length() > 0) { - val commandResult = command match { - case s if (s.startsWith(">>update ")) => - updateFiles(s.stripPrefix(">>update ")) - case s if (s.startsWith(">>compile ")) => - val files = s.stripPrefix(">>compile ") - logWriter.println(prompt + files) - testCompile(files) // In the end, it can finish with an error - case _ => - NestUI.verbose("wrong command in test file: " + command) - false - } - - if (commandResult) loop() - - } else { - NestUI.verbose("finished") - succeeded = true - } - } - - withOutputRedirected(logWriter) { - loop() - testReader.close() - } - - fileManager.mapFile(logFile, "tmp", file, _.replace(sourcepath, "")) - - diffCheck(compareOutput(file, fileBase, kind, logFile)) - } - LogContext(logFile, Some((swr, wr))) - } else - LogContext(logFile, None) - } else - LogContext(logFile, None) - - 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.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) - 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) - (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) - } - } - - withOutputRedirected(logWriter) { - loop(resCompile) - resReader.close() - } - - def replaceSlashes(s: String): String = { - val path = dir.getAbsolutePath+File.separator - // find `path` in `line` - val index = s.indexOf(path) - val line = - if (index != -1) - s.substring(0, index) + s.substring(index + path.length, s.length) - else s - line.replace('\\', '/') - } - - fileManager.mapFile(logFile, "tmp", dir, replaceSlashes) - diffCheck(compareOutput(dir, fileBase, kind, logFile)) - - } 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.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) - - execTest(outDir, logFile, fileBase) - - NestUI.verbose(this+" finished running "+fileBase) - } // successful compile - } catch { // *catch-all* - case e: Exception => - NestUI.verbose("caught "+e) - succeeded = false - } - - diffCheck(compareOutput(dir, fileBase, kind, logFile)) - - LogContext(logFile, Some((swr, wr))) - } else - LogContext(logFile, None) - } - - case "scalap" => { - - 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 byteCode = ByteCode.forClass(clazz) - val result = scala.tools.scalap.Main.decompileScala(byteCode.bytes, 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 - } - - diffCheck(fileManager.compareFiles(logFile, resFile)) - } - } - }) - } - - case "script" => { - // 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.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 (isWin) { - 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() - - diffCheck(compareOutput(file.getParentFile, fileBase, kind, logFile)) - } catch { // *catch-all* - case e: Exception => - NestUI.verbose("caught "+e) - succeeded = false - } - - LogContext(logFile, Some((swr, wr))) - } else - LogContext(logFile, None) - } - } - - def reportAll(results: ImmMap[String, Int], cont: ImmMap[String, Int] => Unit) { - // NestUI.verbose("finished testing "+kind+" with "+errors+" errors") - // NestUI.verbose("created "+compileMgr.numSeparateCompilers+" separate compilers") - timer.cancel() - cont(results) - } - - def reportResult(state: Int, logFile: Option[LogFile], writers: Option[(StringWriter, PrintWriter)]) { - val good = (state == 0) - if (!good) { - errors += 1 - NestUI.verbose("incremented errors: "+errors) - } - - try { - // delete log file only if test was successful - if (good && !logFile.isEmpty && !isPartestDebug) - logFile.get.toDelete = true - - writers match { - case Some((swr, wr)) => - if (state == 2) - printInfoTimeout(wr) - else - printInfoEnd(good, wr) - wr.flush() - swr.flush() - NestUI.normal(swr.toString) - if (state == 1 && fileManager.showDiff && diff != "") - NestUI.normal(diff) - if (state == 1 && fileManager.showLog) - showLog(logFile.get) - case None => - } - } catch { - case npe: NullPointerException => - } - } - - val numFiles = files.size - if (numFiles == 0) - reportAll(ImmMap(), topcont) - - // maps canonical file names to the test result (0: OK, 1: FAILED, 2: TIMOUT) - var 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 = status + (path -> 2) - val swr = new StringWriter - val wr = new PrintWriter(swr) - printInfoStart(res.file, wr) - succeeded = false - reportResult(2, None, Some((swr, wr))) - case Result(_, logs) => - status = status + (path -> (if (succeeded) 0 else 1)) - reportResult( - if (succeeded) 0 else 1, - if (logs != null) Some(logs.file) else None, - if (logs != null) logs.writers else None) - } - if (fileCnt == numFiles) - reportAll(status, topcont) - fileCnt += 1 - } - } - } - } - - private def withOutputRedirected(out: PrintStream)(func: => Unit) { - val oldStdOut = System.out - val oldStdErr = System.err - - try { - System.setOut(out) - System.setErr(out) - func - out.flush() - out.close() - } finally { - System.setOut(oldStdOut) - System.setErr(oldStdErr) - } - } - - private def filesToSet(pre: String, fs: List[String]): Set[AbstractFile] = - fs flatMap (s => Option(AbstractFile getFile (pre + s))) toSet - - private def copyTestFiles(testDir: File, destDir: File) { - val invalidExts = List("changes", "svn", "obj") - testDir.listFiles.toList filter ( - f => (isJavaOrScala(f) && f.isFile) || - (f.isDirectory && !(invalidExts.contains(SFile(f).extension)))) foreach - { f => fileManager.copyFile(f, destDir) } - } - - 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 index e9eda6fb75..abab74de6e 100644 --- a/src/partest/scala/tools/partest/package.scala +++ b/src/partest/scala/tools/partest/package.scala @@ -4,37 +4,61 @@ package scala.tools -import java.io.{ File => JFile } -import nsc.io.{ Path, Process, Directory } -import util.{ PathResolver } -import nsc.Properties.{ propOrElse, propOrNone, propOrEmpty } +import scala.util.control.Exception.catching +import nsc.io.{ File, Path, Process, Directory } +import nsc.util.CommandLineSpec +import Process.runtime +import java.nio.charset.CharacterCodingException package object partest { - import nest.NestUI + /** 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 => "" } - implicit private[partest] def temporaryPath2File(x: Path): JFile = x.jfile - implicit private[partest] def temporaryFile2Path(x: JFile): Path = Path(x) + 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 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) - def basename(name: String): String = Path(name).stripExtension - def resultsToStatistics(results: Iterable[(_, Int)]): (Int, Int) = { - val (files, failures) = results map (_._2 == 0) partition (_ == true) - (files.size, failures.size) - } + private[partest] def toArgs(line: String) = CommandLineSpec toArgs line + private[partest] def fromArgs(args: List[String]) = CommandLineSpec fromArgs args - def vmArgString = { - val str = Process.javaVmArguments mkString " " - "Java VM started with arguments: '%s'" format str - } + /** Strings, argument lists, etc. */ - def allPropertiesString = { - import collection.JavaConversions._ - System.getProperties.toList.sorted map { case (k, v) => "%s -> %s\n".format(k, v) } mkString + 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) - def showAllJVMInfo { - NestUI.verbose(vmArgString) - NestUI.verbose(allPropertiesString) + /** Pretty self explanatory. */ + def printAndExit(msg: String): Unit = { + println(msg) + exit(1) } - def isPartestDebug = propOrEmpty("partest.debug") == "true" + /** 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() = hook } + runtime addShutdownHook t + + try body + finally runtime removeShutdownHook t + } + + /** 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..d20b0659ec --- /dev/null +++ b/src/partest/scala/tools/partest/util/package.scala @@ -0,0 +1,111 @@ +/* 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 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() } + } + + 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 + } +} + +package util { + /** Set any number of alarms up with tuples of the form: + * seconds to alarm -> Function0[Unit] to execute + */ + class Alarmer(alarmTimes: (Int, () => Unit)*) { + import java.util.concurrent._ + + val exec = Executors.newSingleThreadScheduledExecutor() + private def sched(secs: Int, f: () => Unit) = + exec.schedule(new Runnable { def run() = f() }, secs, TimeUnit.SECONDS) + + alarmTimes foreach (sched _).tupled + 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() + } + + // 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() + // } +}
\ No newline at end of file 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) -} |