summaryrefslogtreecommitdiff
path: root/src/partest-alternative
diff options
context:
space:
mode:
Diffstat (limited to 'src/partest-alternative')
-rw-r--r--src/partest-alternative/README50
-rw-r--r--src/partest-alternative/scala/tools/partest/Actions.scala231
-rw-r--r--src/partest-alternative/scala/tools/partest/Alarms.scala86
-rw-r--r--src/partest-alternative/scala/tools/partest/BuildContributors.scala102
-rw-r--r--src/partest-alternative/scala/tools/partest/Categories.scala70
-rw-r--r--src/partest-alternative/scala/tools/partest/Compilable.scala106
-rw-r--r--src/partest-alternative/scala/tools/partest/Config.scala129
-rw-r--r--src/partest-alternative/scala/tools/partest/Dispatcher.scala162
-rw-r--r--src/partest-alternative/scala/tools/partest/Entities.scala74
-rw-r--r--src/partest-alternative/scala/tools/partest/Housekeeping.scala187
-rw-r--r--src/partest-alternative/scala/tools/partest/Partest.scala81
-rw-r--r--src/partest-alternative/scala/tools/partest/PartestSpec.scala104
-rw-r--r--src/partest-alternative/scala/tools/partest/Properties.scala18
-rw-r--r--src/partest-alternative/scala/tools/partest/Results.scala121
-rw-r--r--src/partest-alternative/scala/tools/partest/Runner.scala36
-rw-r--r--src/partest-alternative/scala/tools/partest/Statistics.scala46
-rw-r--r--src/partest-alternative/scala/tools/partest/Universe.scala96
-rw-r--r--src/partest-alternative/scala/tools/partest/ant/JavaTask.scala57
-rw-r--r--src/partest-alternative/scala/tools/partest/antlib.xml3
-rw-r--r--src/partest-alternative/scala/tools/partest/category/AllCategories.scala20
-rw-r--r--src/partest-alternative/scala/tools/partest/category/Analysis.scala64
-rw-r--r--src/partest-alternative/scala/tools/partest/category/Compiler.scala140
-rw-r--r--src/partest-alternative/scala/tools/partest/category/Runner.scala108
-rw-r--r--src/partest-alternative/scala/tools/partest/io/ANSIWriter.scala58
-rw-r--r--src/partest-alternative/scala/tools/partest/io/Diff.java874
-rw-r--r--src/partest-alternative/scala/tools/partest/io/DiffPrint.java607
-rw-r--r--src/partest-alternative/scala/tools/partest/io/JUnitReport.scala38
-rw-r--r--src/partest-alternative/scala/tools/partest/io/Logging.scala137
-rw-r--r--src/partest-alternative/scala/tools/partest/nest/StreamAppender.scala94
-rw-r--r--src/partest-alternative/scala/tools/partest/package.scala45
-rw-r--r--src/partest-alternative/scala/tools/partest/util/package.scala61
31 files changed, 4005 insertions, 0 deletions
diff --git a/src/partest-alternative/README b/src/partest-alternative/README
new file mode 100644
index 0000000000..c7673fe2f8
--- /dev/null
+++ b/src/partest-alternative/README
@@ -0,0 +1,50 @@
+If you're looking for something to read, I suggest running ../test/partest
+with no arguments, which at this moment prints this:
+
+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
+
+ 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
+
+ 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-alternative/scala/tools/partest/Actions.scala b/src/partest-alternative/scala/tools/partest/Actions.scala
new file mode 100644
index 0000000000..cb60152b71
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/Actions.scala
@@ -0,0 +1,231 @@
+/* __ *\
+** ________ ___ / / ___ 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("runExec: " + execEnv.mkString("ENV(", "\n", "\n)"))
+ execCwd foreach (x => trace("CWD(" + x + ")"))
+ }
+
+ trace("runExec: " + cmd)
+ isDryRun || execAndLog(cmd)
+ }
+
+ /** Runs <code>command</code> redirecting standard out and
+ * error out to <code>output</code> file.
+ */
+ private def runCommandOld(command: String, output: java.io.File): Int = {
+ import java.io._
+ import nest.StreamAppender
+
+ // 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 }
+ }
+
+ /** 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 = {
+ runCommandOld(cmd, logFile.jfile) == 0
+
+ // var proc: Process = null
+ //
+ // val result = interruptMeIn(cmd, testTimeout) {
+ // loggingResult {
+ // proc = Process.exec(toArgs(cmd), execEnv, execCwd.orNull, true)
+ // proc.slurp()
+ // }
+ // proc != null && (proc.waitFor() == 0)
+ // }
+ // result getOrElse {
+ // warning("Process never terminated: '%s'" format cmd)
+ // if (proc != null)
+ // proc.destroy()
+ //
+ // false
+ // }
+ }
+ }
+
+ trait ScriptableTest {
+ self: TestEntity =>
+
+ /** Translates a line from a .cmds file into a teststep.
+ */
+ def customTestStep(line: String): TestStep = {
+ trace("customTestStep: " + line)
+ val (cmd, rest) = line span (x => !Character.isWhitespace(x))
+ def qualify(name: String) = sourcesDir / name path
+ val args = toArgs(rest) map qualify
+ 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 _ => 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() = {
+ trace("compile: " + sourceFiles)
+
+ 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 checkFileRequired =
+ returning(checkFile.isFile)(res => if (!res) warnAndLog("A checkFile at '%s' is mandatory.\n" format checkFile.path))
+
+ lazy val sourceFileNames = sourceFiles map (_.name)
+
+ /** Given the difficulty of verifying that any selective approach works
+ * everywhere, the algorithm now is to look for the name of any known
+ * source file for this test, and if seen, remove all the non-whitespace
+ * preceding it. (Paths with whitespace don't work anyway.) This should
+ * wipe out all slashes, backslashes, C:\, cygwin/windows differences,
+ * and whatever else makes a simple diff not simple.
+ *
+ * The log and check file are both transformed, which I don't think is
+ * correct -- only the log should be -- but doing it this way until I
+ * can clarify martin's comments in #3283.
+ */
+ def normalizePaths(s: String) =
+ sourceFileNames.foldLeft(s)((res, name) => res.replaceAll("""\S+\Q%s\E""" format name, name))
+
+ /** The default cleanup normalizes paths relative to sourcesDir,
+ * absorbs line terminator differences by going to lines and back,
+ * and trims leading or trailing whitespace.
+ */
+ def diffCleanup(f: File) = safeLines(f) map normalizePaths mkString "\n" trim
+
+ /** diffFiles requires actual Files as arguments but the output we want
+ * is the post-processed versions of log/check, so we resort to tempfiles.
+ */
+ lazy val diffOutput = {
+ if (!checkFile.exists) "" else {
+ val input = diffCleanup(checkFile)
+ val output = diffCleanup(logFile)
+ def asFile(s: String) = returning(File.makeTemp("partest-diff"))(_ writeAll s)
+
+ if (input == output) ""
+ else diffFiles(asFile(input), asFile(output))
+ }
+ }
+ private def checkTraceName = tracePath(checkFile)
+ private def logTraceName = tracePath(logFile)
+ private def isDiffConfirmed = checkFile.exists && (diffOutput == "")
+
+ private def sendTraceMsg() {
+ def result =
+ if (isDryRun) ""
+ else if (isDiffConfirmed) " [passed]"
+ else if (checkFile.exists) " [failed]"
+ else " [unchecked]"
+
+ trace("diff %s %s%s".format(checkTraceName, logTraceName, result))
+ }
+
+ /** 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() = {
+ sendTraceMsg()
+
+ def updateCheck = (
+ isUpdateCheck && {
+ val formatStr = "** diff %s %s: " + (
+ if (checkFile.exists) "failed, updating '%s' and marking as passed."
+ else if (diffOutput == "") "not creating checkFile at '%s' as there is no output."
+ else "was unchecked, creating '%s' for future tests."
+ ) + "\n"
+
+ normal(formatStr.format(checkTraceName, logTraceName, checkFile.path))
+ if (diffOutput != "") normal(diffOutput)
+
+ checkFile.writeAll(diffCleanup(logFile), "\n")
+ true
+ }
+ )
+
+ isDryRun || isDiffConfirmed || (updateCheck || !checkFile.exists)
+ }
+ }
+}
diff --git a/src/partest-alternative/scala/tools/partest/Alarms.scala b/src/partest-alternative/scala/tools/partest/Alarms.scala
new file mode 100644
index 0000000000..f38d8d6268
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/Alarms.scala
@@ -0,0 +1,86 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools
+package partest
+
+import java.util.{ Timer, TimerTask }
+
+trait Alarms {
+ self: Universe =>
+
+ def interruptMeIn[T](debugMsg: String, seconds: Int)(body: => T): Option[T] = {
+ val thisThread = currentThread
+ val alarm = new SimpleAlarm(seconds * 1000) set thisThread.interrupt()
+ debug("interruptMeIn(%d) '%s'".format(seconds, debugMsg))
+
+ try { Some(body) }
+ catch { case _: InterruptedException => debug("Received interrupted exception.") ; None }
+ finally { debug("Cancelling interruptMeIn '%s'" format debugMsg) ; alarm.cancel() ; Thread.interrupted() }
+ }
+
+ case class AlarmerAction(secs: Int, action: () => Unit) extends Runnable {
+ override def run() = action()
+ }
+
+ /** Set any number of alarms up with tuples of the form:
+ * seconds to alarm -> Function0[Unit] to execute
+ */
+ class Alarmer(alarms: AlarmerAction*) {
+ import java.util.concurrent._
+
+ val exec = Executors.newSingleThreadScheduledExecutor()
+ alarms foreach (x => exec.schedule(x, x.secs, TimeUnit.SECONDS))
+ exec.shutdown()
+
+ def cancelAll() = exec.shutdownNow()
+ }
+
+ class SimpleAlarm(timeout: Long) {
+ private val alarm = new Timer
+
+ /** Start a timer, running the given body if it goes off.
+ */
+ def set(body: => Unit) = returning(new TimerTask { def run() = body })(alarm.schedule(_, timeout))
+
+ /** Cancel the timer.
+ */
+ def cancel() = alarm.cancel()
+ }
+
+ trait TestAlarms {
+ test: TestEntity =>
+
+ private def warning1 = AlarmerAction(testWarning, () => warning(
+ """|I've been waiting %s seconds for this to complete:
+ | %s
+ |It may be stuck, or if not, it should be broken into smaller tests.
+ |""".stripMargin.format(testWarning, test))
+ )
+ private def warning2 = AlarmerAction(testWarning * 2, () => warning(
+ """|Now I've been waiting %s seconds for this to complete:
+ | %s
+ |If partest seems hung it would be a good place to look.
+ |""".stripMargin.format(testWarning * 2, test))
+ )
+
+ def startAlarms(onTimeout: => Unit) =
+ if (isNoAlarms) new Alarmer() // for alarm debugging
+ else new Alarmer(Seq(warning1, warning2, AlarmerAction(testTimeout, () => onTimeout)): _*)
+ }
+
+ // Thread.setDefaultUncaughtExceptionHandler(new UncaughtException)
+ // class UncaughtException extends Thread.UncaughtExceptionHandler {
+ // def uncaughtException(t: Thread, e: Throwable) {
+ // Console.println("Uncaught in %s: %s".format(t, e))
+ // }
+ // }
+ //
+ // lazy val logger = File("/tmp/partest.log").bufferedWriter()
+ // def flog(msg: String) = logger synchronized {
+ // logger write (msg + "\n")
+ // logger.flush()
+ // }
+}
diff --git a/src/partest-alternative/scala/tools/partest/BuildContributors.scala b/src/partest-alternative/scala/tools/partest/BuildContributors.scala
new file mode 100644
index 0000000000..64c7e07bc3
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/BuildContributors.scala
@@ -0,0 +1,102 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2010 LAMP/EPFL
+ */
+
+package scala.tools
+package partest
+
+import nsc.io._
+import nsc.util.ClassPath
+
+trait BuildContributors {
+ universe: Universe =>
+
+ /** A trait mixed into types which contribute a portion of the values.
+ * The basic mechanism is the TestBuild, TestCategory, and TestEntity
+ * can each contribute to each value. They are assembled at the last
+ * moment by the ContributorAssembler (presently the TestEntity.)
+ */
+ trait BuildContributor {
+ def javaFlags: List[String]
+ def scalacFlags: List[String]
+ def classpathPaths: List[Path]
+ def buildProperties: List[(String, Any)]
+ def buildEnvironment: Map[String, String]
+ }
+
+ trait ContributorAssembler {
+ def contributors: List[BuildContributor]
+ def assemble[T](what: BuildContributor => List[T]): List[T] = contributors flatMap what
+
+ /** !!! This will need work if we want to achieve real composability,
+ * but it can wait for the demand.
+ */
+ def assembleScalacArgs(args: List[String]) = assemble(_.scalacFlags) ++ args
+ def assembleJavaArgs(args: List[String]) = assemble(_.javaFlags) ++ args
+ def assembleProperties() = assemble(_.buildProperties)
+ def assembleClasspaths(paths: List[Path]) = assemble(_.classpathPaths) ++ paths
+ def assembleEnvironment() = assemble(_.buildEnvironment.toList).toMap
+
+ def createClasspathString() = ClassPath fromPaths (assembleClasspaths(Nil) : _*)
+ def createPropertyString() = assembleProperties() map { case (k, v) => "-D%s=%s".format(k, v.toString) }
+ }
+
+ trait BuildContribution extends BuildContributor {
+ self: TestBuild =>
+
+ /** The base classpath and system properties.
+ * !!! TODO - this should adjust itself depending on the build
+ * being tested, because pack and quick at least need different jars.
+ */
+ def classpathPaths = List[Path](library, compiler, partest, fjbg) ++ forkJoinPath
+ def buildProperties = List(
+ "scala.home" -> testBuildDir,
+ "partest.lib" -> library, // used in jvm/inner
+ "java.awt.headless" -> true,
+ "user.language" -> "en",
+ "user.country" -> "US",
+ "partest.debug" -> isDebug,
+ "partest.verbose" -> isVerbose
+ // Disabled because there are no natives tests.
+ // "java.library.path" -> srcLibDir
+ )
+ def javaFlags: List[String] = toArgs(javaOpts)
+ def scalacFlags: List[String] = toArgs(scalacOpts)
+
+ /** We put the build being tested's /bin directory in the front of the
+ * path so the scripts and such written to execute "scala" will use this
+ * build and not whatever happens to be on their path.
+ */
+ private def modifiedPath = ClassPath.join(scalaBin.path, Properties.envOrElse("PATH", ""))
+ def buildEnvironment = Map("PATH" -> modifiedPath)
+ }
+
+ trait CategoryContribution extends BuildContributor {
+ self: DirBasedCategory =>
+
+ /** Category-wide classpath additions placed in <category>/lib. */
+ private def libContents = root / "lib" ifDirectory (_.list.toList)
+
+ def classpathPaths = libContents getOrElse Nil
+ def buildProperties = Nil
+ def javaFlags = Nil
+ def scalacFlags = Nil
+ def buildEnvironment = Map()
+ }
+
+ trait TestContribution extends BuildContributor with ContributorAssembler {
+ self: TestEntity =>
+
+ def jarsInTestDir = location.walk collect { case f: File if f hasExtension "jar" => f } toList
+
+ def contributors = List(build, category, self)
+ def javaFlags = safeArgs(javaOptsFile)
+ def scalacFlags = safeArgs(scalaOptsFile)
+ def classpathPaths = jarsInTestDir :+ outDir
+ def buildProperties = List(
+ "partest.output" -> outDir.toAbsolute, // used in jvm/inner
+ "partest.cwd" -> outDir.parent.toAbsolute // used in shootout tests
+ )
+ def buildEnvironment = Map("JAVA_OPTS" -> fromArgs(assembleJavaArgs(Nil)))
+ }
+} \ No newline at end of file
diff --git a/src/partest-alternative/scala/tools/partest/Categories.scala b/src/partest-alternative/scala/tools/partest/Categories.scala
new file mode 100644
index 0000000000..172cca74b4
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/Categories.scala
@@ -0,0 +1,70 @@
+/* __ *\
+** ________ ___ / / ___ 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 checkFileRequired: TestStep = (_: TestEntity).checkFileRequired
+ 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-alternative/scala/tools/partest/Compilable.scala b/src/partest-alternative/scala/tools/partest/Compilable.scala
new file mode 100644
index 0000000000..ddaa277842
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/Compilable.scala
@@ -0,0 +1,106 @@
+/* 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 = {
+ val allArgString = fromArgs(javacpArg :: javacOpts :: args)
+
+ // javac -d outdir -classpath <basepath> <files>
+ val cmd = "%s -d %s %s".format(javacCmd, outDir, allArgString)
+ 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)
+ def nonFileArgs = if (isVerbose) global.settings.recreateArgs else assembleScalacArgs(Nil)
+ def traceArgs = fromArgs(nonFileArgs ++ (files map tracePath))
+ def traceMsg = "scalac " + traceArgs
+
+ trace(traceMsg)
+ isDryRun || global.partestCompile(files, 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("runScala: " + 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)
+ case ae: AssertionError => creporter.error(null, ""+ae)
+ case te: TypeError => creporter.error(null, ""+te)
+ case ex =>
+ creporter.error(null, ""+ex)
+ throw ex
+ }
+
+ 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)
+ }
+ }
+}
diff --git a/src/partest-alternative/scala/tools/partest/Config.scala b/src/partest-alternative/scala/tools/partest/Config.scala
new file mode 100644
index 0000000000..288a3034e9
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/Config.scala
@@ -0,0 +1,129 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2010 LAMP/EPFL
+ */
+
+package scala.tools
+package partest
+
+import io._
+import nsc.io._
+import Properties._
+
+trait Config {
+ universe: Universe =>
+
+ lazy val src = absolutize(srcDir).toDirectory
+ lazy val build = new TestBuild()
+
+ def javaHomeEnv = envOrElse("JAVA_HOME", null)
+ def javaCmd = envOrElse("JAVACMD", "java")
+ def javacCmd = Option(javaHomeEnv) map (x => Path(x) / "bin" / "javac" path) getOrElse "javac"
+
+ /** Values related to actors. The timeouts are in seconds. On a dry
+ * run we only allocate one worker so the output isn't interspersed.
+ */
+ def workerTimeout = 3600 // 1 hour, probably overly generous
+ def numWorkers = if (isDryRun) 1 else propOrElse("partest.actors", "8").toInt
+ def expectedErrors = propOrElse("partest.errors", "0").toInt
+ def poolSize = (wrapAccessControl(propOrNone("actors.corePoolSize")) getOrElse "16").toInt
+
+ def allScalaFiles = src.deepFiles filter (_ hasExtension "scala")
+ def allObjDirs = src.deepDirs filter (_ hasExtension "obj")
+ def allLogFiles = src.deepFiles filter (_ hasExtension "log")
+ def allClassFiles = src.deepFiles filter (_ hasExtension "class")
+
+ class TestBuild() extends BuildContribution {
+ import nsc.util.ClassPath
+
+ /** Scala core libs.
+ */
+ val library = pathForComponent("library")
+ val compiler = pathForComponent("compiler")
+ val partest = pathForComponent("partest")
+ val scalap = pathForComponent("scalap", "%s.jar")
+
+ /** Scala supplementary libs - these are not all needed for all build targets,
+ * and some of them are copied inside other jars in later targets. However quick
+ * for instance cannot be run without some of these.
+ */
+ val fjbg = pathForLibrary("fjbg")
+ val msil = pathForLibrary("msil")
+ val forkjoin = pathForLibrary("forkjoin")
+ val scalacheck = pathForLibrary("scalacheck")
+
+ /** Other interesting paths.
+ */
+ val scalaBin = testBuildDir / "bin"
+
+ /** A hack for now to get quick running.
+ */
+ def needsForkJoin = {
+ val loader = nsc.util.ScalaClassLoader.fromURLs(List(library.toURL))
+ val fjMarker = "scala.concurrent.forkjoin.ForkJoinTask"
+ val clazz = loader.tryToLoadClass(fjMarker)
+
+ if (clazz.isDefined) debug("Loaded ForkJoinTask OK, don't need jar.")
+ else debug("Could not load ForkJoinTask, putting jar on classpath.")
+
+ clazz.isEmpty
+ }
+ lazy val forkJoinPath: List[Path] = if (needsForkJoin) List(forkjoin) else Nil
+
+ /** Internal **/
+ private def repo = partestDir.parent.normalize
+ // XXX - is this needed? Where?
+ //
+ // private val pluginOptionString = "-Xplugin:"
+ // private def updatedPluginPath(options: String): String = {
+ // val (pluginArgs, rest) = toArgs(options) partition (_ startsWith pluginOptionString)
+ // // join all plugin paths as one classpath
+ // val pluginPaths = ClassPath.join(pluginArgs map (_ stripPrefix pluginOptionString): _*)
+ // // map all paths to absolute
+ // val newPath = ClassPath.map(pluginPaths, x => absolutize(x).path)
+ // // recreate option
+ // val pluginOption = if (newPath == "") None else Some(pluginOptionString + newPath)
+ //
+ // fromArgs(rest ::: pluginOption.toList)
+ // }
+
+ private def pathForComponent(what: String, jarFormat: String = "scala-%s.jar"): Path = {
+ def asDir = testBuildDir / "classes" / what
+ def asJar = testBuildDir / "lib" / jarFormat.format(what)
+
+ if (asDir.isDirectory) asDir
+ else if (asJar.isFile) asJar
+ else ""
+ }
+ private def pathForLibrary(what: String) = File(repo / "lib" / (what + ".jar"))
+ }
+
+ def printConfigBanner() = {
+ debug("Java VM started with arguments: '%s'" format fromArgs(Process.javaVmArguments))
+ debug("System Properties:\n" + util.allPropertiesString())
+
+ normal(configBanner())
+ }
+
+ /** Treat an access control failure as None. */
+ private def wrapAccessControl[T](body: => Option[T]): Option[T] =
+ try body catch { case _: java.security.AccessControlException => None }
+
+ private def configBanner() = {
+ val javaBin = Path(javaHome) / "bin"
+ val javaInfoString = "%s (build %s, %s)".format(javaVmName, javaVmVersion, javaVmInfo)
+
+ List(
+ "Scala compiler classes in: " + testBuildDir,
+ "Scala version is: " + nsc.Properties.versionMsg,
+ "Scalac options are: " + universe.scalacOpts,
+ "Java binaries in: " + javaBin,
+ "Java runtime is: " + javaInfoString,
+ "Java runtime options: " + (Process.javaVmArguments mkString " "),
+ "Javac options are: " + universe.javacOpts,
+ "Java options are: " + universe.javaOpts,
+ "Source directory is: " + src,
+ "Selected categories: " + (selectedCategories mkString " "),
+ ""
+ ) mkString "\n"
+ }
+}
diff --git a/src/partest-alternative/scala/tools/partest/Dispatcher.scala b/src/partest-alternative/scala/tools/partest/Dispatcher.scala
new file mode 100644
index 0000000000..2a9d99ab60
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/Dispatcher.scala
@@ -0,0 +1,162 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2010 LAMP/EPFL
+ * @author Philipp Haller
+ */
+
+package scala.tools
+package partest
+
+import scala.tools.nsc.io._
+import scala.actors.{ Actor, TIMEOUT }
+import scala.actors.Actor._
+import scala.collection.immutable
+import scala.util.control.Exception.ultimately
+
+/** The machinery for concurrent execution of tests. Each Worker
+ * is given a bundle of tests, which it runs sequentially and then
+ * sends a report back to the dispatcher.
+ */
+trait Dispatcher {
+ partest: Universe =>
+
+ /** The public entry point. The given filter narrows down the list of
+ * tests to run.
+ */
+ def runSelection(categories: List[TestCategory], filt: TestEntity => Boolean = _ => true): CombinedTestResults = {
+ // Setting scala.home informs tests where to obtain their jars.
+ setProp("scala.home", testBuildDir.path)
+
+ val allTests = allCategories flatMap (_.enumerate)
+ val selected = allTests filter filt
+ val groups = selected groupBy (_.category)
+ val count = selected.size
+
+ if (count == 0) return CombinedTestResults(0, 0, 0, Nil)
+ 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, TestResult] = {
+ // 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)
+ // mark all the worker's tests as having timed out - should be hard to miss
+ // immutable.Map[TestEntity, TestResult]()
+ groups(w.workerNum) map (x => (x -> new Timeout(x))) 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 mapValues (_.state))
+ val failures = resultMap.values filterNot (_.passed) toList
+
+ CombinedTestResults(passed, failed, milliSeconds, failures)
+ }
+
+ /** 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, TestResult] => Unit) {
+ var results = new immutable.HashMap[TestEntity, TestResult] // 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 -> result)
+
+ // show on screen
+ if (isDryRun) normal("\n") // blank line between dry run traces
+ else result show extraMessage
+
+ // remove log if successful
+ if (result.passed)
+ test.deleteLog()
+
+ // Respond to master if this Worker is complete
+ if (isComplete)
+ onCompletion(results)
+ }
+
+ Actor.loopWhile(testIterator.hasNext) {
+ val parent = self
+ // pick a test and set some alarms
+ val test = testIterator.next
+ val alarmer = test startAlarms (parent ! new Timeout(test))
+
+ actor {
+ ultimately(alarmer.cancelAll()) {
+ // Calling isSuccess forces the lazy val "process" inside the test, running it.
+ val res = test.isSuccess
+ // Cancel the alarms and alert the media.
+ parent ! TestResult(test, res)
+ }
+ }
+
+ react {
+ case x: TestResult => countAndReport(x)
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/partest-alternative/scala/tools/partest/Entities.scala b/src/partest-alternative/scala/tools/partest/Entities.scala
new file mode 100644
index 0000000000..bea505b594
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/Entities.scala
@@ -0,0 +1,74 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2010 LAMP/EPFL
+ * @author Philipp Haller
+ */
+
+package scala.tools
+package partest
+
+import nsc.io._
+
+trait Entities {
+ self: Universe =>
+
+ abstract class TestEntity extends AbsTestEntity
+ with TestContribution
+ with TestHousekeeping
+ with TestAlarms
+ with EntityLogging
+ with CompilableTest
+ with ScriptableTest
+ with DiffableTest {
+ def location: Path
+ def category: TestCategory
+
+ lazy val label = location.stripExtension
+ lazy val testClasspath = returning(createClasspathString())(x => vtrace("testClasspath: " + x))
+
+ /** 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)
+
+ /** 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() // checkFile, logFile
+
+ /** The memoized result of the test run.
+ */
+ private lazy val process = {
+ val outcome = runWrappers(testSequence.actions forall (f => f(this)))
+
+ // an empty outcome means we've been interrupted and are shutting down.
+ outcome getOrElse false
+ }
+ }
+
+ case class TestDirectory(category: TestCategory, location: Directory) extends TestEntity { }
+ case class TestFile(category: TestCategory, location: File) extends TestEntity { }
+}
diff --git a/src/partest-alternative/scala/tools/partest/Housekeeping.scala b/src/partest-alternative/scala/tools/partest/Housekeeping.scala
new file mode 100644
index 0000000000..a624ca8adb
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/Housekeeping.scala
@@ -0,0 +1,187 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2010 LAMP/EPFL
+ */
+
+package scala.tools
+package partest
+
+import scala.util.control.Exception.catching
+import util._
+import nsc.io._
+import Process.runtime
+import Properties._
+
+/** An agglomeration of code which is low on thrills. Hopefully
+ * it operates so quietly in the background that you never have to
+ * look at this file.
+ */
+trait Housekeeping {
+ self: Universe =>
+
+ /** Orderly shutdown on ctrl-C. */
+ @volatile private var _shuttingDown = false
+ protected def setShuttingDown() = {
+ /** Whatever we want to do as shutdown begins goes here. */
+ if (!_shuttingDown) {
+ warning("Received shutdown signal, partest is cleaning up...\n")
+ _shuttingDown = true
+ }
+ }
+ def isShuttingDown = _shuttingDown
+
+ /** Execute some code with a shutdown hook in place. This is
+ * motivated by the desire not to leave the filesystem full of
+ * junk when someone ctrl-Cs a test run.
+ */
+ def withShutdownHook[T](hook: => Unit)(body: => T): Option[T] =
+ /** Java doesn't like it if you keep adding and removing shutdown
+ * hooks after shutdown has begun, so we trap the failure.
+ */
+ catching(classOf[IllegalStateException]) opt {
+ val t = new Thread() {
+ override def run() = {
+ setShuttingDown()
+ hook
+ }
+ }
+ runtime addShutdownHook t
+
+ try body
+ finally runtime removeShutdownHook t
+ }
+
+ /** Search for a directory, possibly given only a name, by starting
+ * at the current dir and walking upward looking for it at each level.
+ */
+ protected def searchForDir(name: String): Directory = {
+ val result = Path(name) ifDirectory (x => x.normalize) orElse {
+ val cwd = Directory.Current getOrElse error("user.dir property not set")
+ val dirs = cwd :: cwd.parents map (_ / name)
+
+ Path onlyDirs dirs map (_.normalize) headOption
+ }
+
+ result getOrElse error("Fatal: could not find directory '%s'" format name)
+ }
+
+ /** Paths we ignore for most purposes.
+ */
+ def ignorePath(x: Path) = {
+ (x.name startsWith ".") ||
+ (x.isDirectory && ((x.name == "lib") || x.hasExtension("obj", "svn")))
+ }
+ /** Make a possibly relative path absolute using partestDir as the base.
+ */
+ def absolutize(path: String) = Path(path) toAbsoluteWithRoot partestDir
+
+ /** Go on a deleting binge.
+ */
+ def cleanupAll() {
+ if (isNoCleanup)
+ return
+
+ val (dirCount, fileCount) = (cleanupObjDirs(), cleanupLogs() + cleanupJunk())
+ if (dirCount + fileCount > 0)
+ normal("Cleaned up %d directories and %d files.\n".format(dirCount, fileCount))
+ }
+
+ def cleanupObjDirs() = countTrue(allObjDirs collect { case x if x.exists => x.deleteRecursively() })
+ def cleanupJunk() = countTrue(allClassFiles collect { case x if x.exists => x.delete() })
+ def cleanupLogs() = countTrue(allLogFiles collect { case x if x.exists => x.delete() })
+
+ /** Look through every file in the partest directory and ask around
+ * to make sure someone knows him. Complain about strangers.
+ */
+ def validateAll() {
+ def denotesTest(p: Path) = allCategories exists (_ denotesTest p)
+ def isMSILcheck(p: Path) = p.name endsWith "-msil.check"
+
+ def analyzeCategory(cat: DirBasedCategory) = {
+ val allTests = cat.enumerate
+ val otherPaths = cat.root walkFilter (x => !ignorePath(x)) filterNot (cat denotesTest _) filterNot isMSILcheck toList
+ val count = otherPaths.size
+
+ println("Validating %d non-test paths in %s.".format(count, cat.kind))
+
+ for (path <- otherPaths) {
+ (allTests find (_ acknowledges path)) match {
+ case Some(test) => if (isVerbose) println(" OK: '%s' is claimed by '%s'".format(path, test.label))
+ case _ => println(">> Unknown path '%s'" format path)
+ }
+ }
+ }
+
+ allCategories collect { case x: DirBasedCategory => analyzeCategory(x) }
+ }
+
+ trait TestHousekeeping {
+ self: TestEntity =>
+
+ /** Calculating derived files. Given a test like
+ * files/run/foo.scala or files/run/foo/
+ * This creates paths like foo.check, foo.flags, etc.
+ */
+ def withExtension(extension: String) = categoryDir / "%s.%s".format(label, extension)
+
+ /** True for a path if this test acknowledges it belongs to this test.
+ * Overridden by some categories.
+ */
+ def acknowledges(path: Path): Boolean = {
+ val loc = location.normalize
+ val knownPaths = List(scalaOptsFile, javaOptsFile, commandFile, logFile, checkFile) ++ jarsInTestDir
+ def isContainedSource = location.isDirectory && isJavaOrScala(path) && (path.normalize startsWith loc)
+
+ (knownPaths exists (_ isSame path)) || isContainedSource
+ }
+
+ /** This test "responds to" this String. This could mean anything -- it's a
+ * way of specifying ad-hoc collections of tests to exercise only a subset of tests.
+ * At present it looks for the given String in all the test sources.
+ */
+ def respondsToString(str: String) = containsString(str)
+ def containsString(str: String) = {
+ debug("Checking %s for \"%s\"".format(sourceFiles mkString ", ", str))
+ sourceFiles map safeSlurp exists (_ contains str)
+ }
+
+ def possiblyTimed[T](body: => T): T = {
+ if (isStats) timed(recordTestTiming(label, _))(body)
+ else body
+ }
+
+ private def prepareForTestRun() = {
+ // make sure we have a clean slate
+ deleteLog(force = true)
+ if (outDir.exists)
+ outDir.deleteRecursively()
+
+ // recreate object dir
+ outDir createDirectory true
+ }
+ def deleteOutDir() = outDir.deleteRecursively()
+ def deleteShutdownHook() = { debug("Shutdown hook deleting " + outDir) ; deleteOutDir() }
+
+ protected def runWrappers[T](body: => T): Option[T] = {
+ prepareForTestRun()
+
+ withShutdownHook(deleteShutdownHook()) {
+ loggingOutAndErr {
+ val result = possiblyTimed { body }
+ if (!isNoCleanup)
+ deleteOutDir()
+
+ result
+ }
+ }
+ }
+
+ override def toString = location.path
+ override def equals(other: Any) = other match {
+ case x: TestEntity => location.normalize == x.location.normalize
+ case _ => false
+ }
+ override def hashCode = location.normalize.hashCode
+ }
+
+ private def countTrue(f: => Iterator[Boolean]) = f filter (_ == true) length
+} \ No newline at end of file
diff --git a/src/partest-alternative/scala/tools/partest/Partest.scala b/src/partest-alternative/scala/tools/partest/Partest.scala
new file mode 100644
index 0000000000..b3fe9a98ef
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/Partest.scala
@@ -0,0 +1,81 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2010 LAMP/EPFL
+ */
+
+package scala.tools
+package partest
+
+import nsc.io._
+import nsc.util._
+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 = PartestSpec(args: _*)
+} with Universe with PartestSpec with cmd.Instance with AllCategories {
+
+ if (parsed.propertyArgs.nonEmpty)
+ debug("Partest property args: " + fromArgs(parsed.propertyArgs))
+
+ debug("Partest created with args: " + fromArgs(args))
+
+ def helpMsg = PartestSpec.helpMsg
+
+ // 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
+
+ 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() = {
+ def onTimeout() = {
+ warning("Partest test run timed out after " + timeout + " seconds.\n")
+ System.exit(-1)
+ }
+ val alarm = new Alarmer(AlarmerAction(timeout, () => onTimeout()))
+
+ try runSelection(selectedCategories, filter)
+ finally alarm.cancelAll()
+ }
+}
+
+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-alternative/scala/tools/partest/PartestSpec.scala b/src/partest-alternative/scala/tools/partest/PartestSpec.scala
new file mode 100644
index 0000000000..c25119b3af
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/PartestSpec.scala
@@ -0,0 +1,104 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools
+package partest
+
+import nsc.io._
+import cmd._
+
+/** 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 Spec with Meta.StdOpts with Interpolation {
+ def referenceSpec = PartestSpec
+ def programInfo = Spec.Info("partest", "", "scala.tools.partest.Runner")
+ private val kind = new Spec.Accumulator[String]()
+ protected def testKinds = kind.get
+
+ private implicit val tokenizeString = FromString.ArgumentsFromString // String => List[String]
+
+ help("""
+ |# Pro Tip! Instant bash completion: `partest --bash` (note backticks)
+ |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
+ |
+ | Test categories:""".stripMargin)
+
+ 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" defaultTo "test"
+ val buildDir = "builddir" / "path from ~ to test build" defaultTo "build/pack"
+ val srcDir = "srcdir" / "path from --rootdir to sources" defaultTo "files"
+ val javaOpts = "javaopts" / "flags to java on all runs" defaultToEnv "JAVA_OPTS"
+ val javacOpts = "javacopts" / "flags to javac on all runs" defaultToEnv "JAVAC_OPTS"
+ val scalacOpts = "scalacopts" / "flags to scalac on all tests" defaultToEnv "SCALAC_OPTS"
+
+ "pack" / "" expandTo ("--builddir", "build/pack")
+ "quick" / "" expandTo ("--builddir", "build/quick")
+
+ heading ("Options influencing output:")
+ val isTrace = "trace" / "show the individual steps taken by each test" --?
+ val isShowDiff = "show-diff" / "show diff between log and check file" --?
+ val isShowLog = "show-log" / "show log on failures" --?
+ val isDryRun = "dry-run" / "do not run tests, only show their traces." --?
+ val isTerse = "terse" / "be less verbose (almost silent except for failures)" --?
+ val isVerbose = "verbose" / "be more verbose (additive with --trace)" --?
+ val isDebug = "debug" / "maximum debugging output" --?
+ val isAnsi = "ansi" / "print output in color" --?
+
+ heading ("Other options:")
+ val timeout = "timeout" / "Overall timeout in seconds" defaultTo 7200
+ val testWarning = "test-warning" / "Test warning in seconds" defaultTo 90
+ val testTimeout = "test-timeout" / "Test timeout in seconds" defaultTo 900
+ val isCleanup = "cleanup" / "delete all stale files and dirs before run" --?
+ val isNoCleanup = "nocleanup" / "do not delete any logfiles or object dirs" --?
+ val isStats = "stats" / "collect and print statistics about the tests" --?
+ val isValidate = "validate" / "examine test filesystem for inconsistencies" --?
+ val isUpdateCheck = "update-check" / "overwrite checkFile if diff fails" --?
+
+ "version" / "print version" --> runAndExit(println(Properties.versionMsg))
+
+ // no help for anything below this line - secret options
+ // mostly intended for property configuration.
+ val runSets = ("runsets" --^) getOrElse Nil
+ val isNoAlarms = "noalarms" --?
+ val isInsideAnt = "is-in-ant" --?
+}
+
+object PartestSpec extends PartestSpec with Property {
+ lazy val propMapper = new PropertyMapper(PartestSpec) {
+ override def isPassThrough(key: String) = key == "partest.options"
+ }
+
+ type ThisCommandLine = PartestCommandLine
+ class PartestCommandLine(args: List[String]) extends SpecCommandLine(args) {
+ override def errorFn(msg: String) = printAndExit("Error: " + msg)
+
+ def propertyArgs = PartestSpec.propertyArgs
+ }
+
+ override def creator(args: List[String]): PartestCommandLine = new PartestCommandLine(args)
+}
diff --git a/src/partest-alternative/scala/tools/partest/Properties.scala b/src/partest-alternative/scala/tools/partest/Properties.scala
new file mode 100644
index 0000000000..4eeb0359ec
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/Properties.scala
@@ -0,0 +1,18 @@
+/* __ *\
+** ________ ___ / / ___ Scala Parallel Testing **
+** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
+** /____/\___/_/ |_/____/_/ | | **
+** |/ **
+\* */
+
+// $Id$
+
+package scala.tools
+package partest
+
+/** Loads partest.properties from the jar. */
+object Properties extends scala.util.PropertiesTrait {
+ protected def propCategory = "partest"
+ protected def pickJarBasedOn = classOf[Application]
+}
diff --git a/src/partest-alternative/scala/tools/partest/Results.scala b/src/partest-alternative/scala/tools/partest/Results.scala
new file mode 100644
index 0000000000..5d0e300136
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/Results.scala
@@ -0,0 +1,121 @@
+/* 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, TestResult])
+
+ /** 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, description)
+ }
+
+ 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))
+ }
+ override def toString = List(super.toString, toStringTrunc(entity.failureMessage(), 400)) mkString "\n"
+ }
+ 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,
+ failures: List[TestResult]
+ ) {
+ // 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,
+ failures ::: x.failures
+ )
+
+ def elapsedString = "%02d:%02d:%02d".format(elapsedHrs, dispMins, dispSecs)
+ def failuresString = {
+ if (failures.isEmpty) ""
+ else "Summary of failures:" :: failures mkString ("\n", "\n", "")
+ }
+
+ 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) + failuresString
+ else "All %d tests were successful (elapsed time: %s)".format(total, elapsedString)
+ }
+} \ No newline at end of file
diff --git a/src/partest-alternative/scala/tools/partest/Runner.scala b/src/partest-alternative/scala/tools/partest/Runner.scala
new file mode 100644
index 0000000000..1a28e60896
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/Runner.scala
@@ -0,0 +1,36 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2010 LAMP/EPFL
+ * @author Philipp Haller
+ */
+
+package scala.tools
+package partest
+
+import nsc.io._
+
+object Runner {
+ def main(args: Array[String]) {
+ val runner = Partest(args: _*)
+ import runner._
+
+ 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-alternative/scala/tools/partest/Statistics.scala b/src/partest-alternative/scala/tools/partest/Statistics.scala
new file mode 100644
index 0000000000..2ea3c6e8f0
--- /dev/null
+++ b/src/partest-alternative/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-alternative/scala/tools/partest/Universe.scala b/src/partest-alternative/scala/tools/partest/Universe.scala
new file mode 100644
index 0000000000..942fc1a8be
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/Universe.scala
@@ -0,0 +1,96 @@
+/* __ *\
+** ________ ___ / / ___ Scala Parallel Testing **
+** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
+** /____/\___/_/ |_/____/_/ | | **
+** |/ **
+\* */
+
+package scala.tools
+package partest
+
+import nsc.io._
+import category.AllCategories
+import io.Logging
+
+/** The high level view of the partest infrastructure.
+ */
+abstract class Universe
+ extends Entities
+ with BuildContributors
+ with Logging
+ with Dispatcher
+ with Statistics
+ with Housekeeping
+ with Results
+ with PartestCompilation
+ with PartestSpec
+ with Config
+ with Alarms
+ with Actions
+ with Categories {
+
+ /** The abstract values from which all else is derived. */
+ def partestDir: Directory
+ def testBuildDir: Directory
+ def allCategories: List[TestCategory]
+ def selectedCategories: List[TestCategory]
+
+ /** Some plausibly abstract types. */
+ type TestBuild <: BuildContributor // e.g. quick, pack
+ type TestCategory <: AbsTestCategory // e.g. pos, neg, run
+ type TestEntity <: AbsTestEntity // e.g. files/pos/test25.scala
+ type TestSequence <: AbsTestSequence // e.g. compile, run, diff
+
+ /** Although TestStep isn't much more than Function1 right now,
+ * it exists this way so it can become more capable.
+ */
+ implicit def f1ToTestStep(f: TestEntity => Boolean): TestStep =
+ new TestStep { def apply(test: TestEntity) = f(test) }
+
+ abstract class TestStep extends (TestEntity => Boolean) {
+ def apply(test: TestEntity): Boolean
+ }
+
+ /** An umbrella category of tests, such as "pos" or "run".
+ */
+ trait AbsTestCategory extends BuildContributor {
+ type TestSettings
+
+ def kind: String
+ def testSequence: TestSequence
+ def denotesTest(location: Path): Boolean
+
+ def createTest(location: Path): TestEntity
+ def createSettings(entity: TestEntity): TestSettings
+ def enumerate: List[TestEntity]
+ }
+
+ /** A single test. It may involve multiple files, but only a
+ * single path is used to designate it.
+ */
+ trait AbsTestEntity extends BuildContributor {
+ def category: TestCategory
+ def location: Path
+ def onException(x: Throwable): Unit
+ def testClasspath: String
+
+ /** 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-alternative/scala/tools/partest/ant/JavaTask.scala b/src/partest-alternative/scala/tools/partest/ant/JavaTask.scala
new file mode 100644
index 0000000000..6740554dd8
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/ant/JavaTask.scala
@@ -0,0 +1,57 @@
+/* __ *\
+** ________ ___ / / ___ 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.Environment
+
+import scala.tools.nsc.io._
+import scala.tools.nsc.util.ClassPath
+import cmd.Spec._
+
+class JavaTask extends Java {
+ override def getTaskName() = "partest"
+ private val scalaRunnerClass = "scala.tools.nsc.MainGenericRunner"
+ private val partestRunnerClass = "scala.tools.partest.Runner"
+ def defaultJvmArgs = "-Xms64M -Xmx768M -Xss768K -XX:MaxPermSize=96M"
+
+ protected def rootDir = prop("partest.rootdir") getOrElse (baseDir / "test").path
+ protected def partestJVMArgs = prop("partest.jvm.args") getOrElse defaultJvmArgs
+ protected def runnerArgs = List("-usejavacp", partestRunnerClass, "--javaopts", partestJVMArgs)
+
+ private def baseDir = Directory(getProject.getBaseDir)
+ private def prop(s: String) = Option(getProject getProperty s)
+ private def jvmline(s: String) = returning(createJvmarg())(_ setLine s)
+ private def addArg(s: String) = returning(createArg())(_ setValue s)
+
+ private def newKeyValue(key: String, value: String) =
+ returning(new Environment.Variable)(x => { x setKey key ; x setValue value })
+
+ def setDefaults() {
+ setFork(true)
+ setFailonerror(true)
+ getProject.setSystemProperties()
+ setClassname(scalaRunnerClass)
+ addSysproperty(newKeyValue("partest.is-in-ant", "true"))
+ jvmline(partestJVMArgs)
+ runnerArgs foreach addArg
+
+ // do we want basedir or rootDir to be the cwd?
+ // setDir(Path(rootDir).jfile)
+ }
+
+ override def init() = {
+ super.init()
+ setDefaults()
+ }
+}
+
diff --git a/src/partest-alternative/scala/tools/partest/antlib.xml b/src/partest-alternative/scala/tools/partest/antlib.xml
new file mode 100644
index 0000000000..af36f11368
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/antlib.xml
@@ -0,0 +1,3 @@
+<antlib>
+ <taskdef name="partest" classname="scala.tools.partest.ant.JavaTask"/>
+</antlib>
diff --git a/src/partest-alternative/scala/tools/partest/category/AllCategories.scala b/src/partest-alternative/scala/tools/partest/category/AllCategories.scala
new file mode 100644
index 0000000000..953f80324b
--- /dev/null
+++ b/src/partest-alternative/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(checkFileRequired, 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-alternative/scala/tools/partest/category/Analysis.scala b/src/partest-alternative/scala/tools/partest/category/Analysis.scala
new file mode 100644
index 0000000000..2c6c208ee5
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/category/Analysis.scala
@@ -0,0 +1,64 @@
+/* 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(checkFileRequired, 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")
+
+ 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-alternative/scala/tools/partest/category/Compiler.scala b/src/partest-alternative/scala/tools/partest/category/Compiler.scala
new file mode 100644
index 0000000000..49775d5031
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/category/Compiler.scala
@@ -0,0 +1,140 @@
+/* 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(checkFileRequired, 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 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(checkFileRequired, 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 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("send: " + (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-alternative/scala/tools/partest/category/Runner.scala b/src/partest-alternative/scala/tools/partest/category/Runner.scala
new file mode 100644
index 0000000000..10bf5794a9
--- /dev/null
+++ b/src/partest-alternative/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("%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-alternative/scala/tools/partest/io/ANSIWriter.scala b/src/partest-alternative/scala/tools/partest/io/ANSIWriter.scala
new file mode 100644
index 0000000000..0ddcd97a5f
--- /dev/null
+++ b/src/partest-alternative/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-alternative/scala/tools/partest/io/Diff.java b/src/partest-alternative/scala/tools/partest/io/Diff.java
new file mode 100644
index 0000000000..c7a3d42f30
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/io/Diff.java
@@ -0,0 +1,874 @@
+// $Id$
+
+package scala.tools.partest.io;
+
+import java.util.Hashtable;
+
+/** A class to compare IndexedSeqs of objects. The result of comparison
+ is a list of <code>change</code> objects which form an
+ edit script. The objects compared are traditionally lines
+ of text from two files. Comparison options such as "ignore
+ whitespace" are implemented by modifying the <code>equals</code>
+ and <code>hashcode</code> methods for the objects compared.
+<p>
+ The basic algorithm is described in: </br>
+ "An O(ND) Difference Algorithm and its Variations", Eugene Myers,
+ Algorithmica Vol. 1 No. 2, 1986, p 251.
+<p>
+ This class outputs different results from GNU diff 1.15 on some
+ inputs. Our results are actually better (smaller change list, smaller
+ total size of changes), but it would be nice to know why. Perhaps
+ there is a memory overwrite bug in GNU diff 1.15.
+
+ @author Stuart D. Gathman, translated from GNU diff 1.15
+ Copyright (C) 2000 Business Management Systems, Inc.
+<p>
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 1, or (at your option)
+ any later version.
+<p>
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+<p>
+ You should have received a copy of the <a href=COPYING.txt>
+ GNU General Public License</a>
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+
+public class Diff {
+
+ /** Prepare to find differences between two arrays. Each element of
+ the arrays is translated to an "equivalence number" based on
+ the result of <code>equals</code>. The original Object arrays
+ are no longer needed for computing the differences. They will
+ be needed again later to print the results of the comparison as
+ an edit script, if desired.
+ */
+ public Diff(Object[] a,Object[] b) {
+ Hashtable h = new Hashtable(a.length + b.length);
+ filevec[0] = new file_data(a,h);
+ filevec[1] = new file_data(b,h);
+ }
+
+ /** 1 more than the maximum equivalence value used for this or its
+ sibling file. */
+ private int equiv_max = 1;
+
+ /** When set to true, the comparison uses a heuristic to speed it up.
+ With this heuristic, for files with a constant small density
+ of changes, the algorithm is linear in the file size. */
+ public boolean heuristic = false;
+
+ /** When set to true, the algorithm returns a guarranteed minimal
+ set of changes. This makes things slower, sometimes much slower. */
+ public boolean no_discards = false;
+
+ private int[] xvec, yvec; /* IndexedSeqs being compared. */
+ private int[] fdiag; /* IndexedSeq, indexed by diagonal, containing
+ the X coordinate of the point furthest
+ along the given diagonal in the forward
+ search of the edit matrix. */
+ private int[] bdiag; /* IndexedSeq, indexed by diagonal, containing
+ the X coordinate of the point furthest
+ along the given diagonal in the backward
+ search of the edit matrix. */
+ private int fdiagoff, bdiagoff;
+ private final file_data[] filevec = new file_data[2];
+ private int cost;
+
+ /** Find the midpoint of the shortest edit script for a specified
+ portion of the two files.
+
+ We scan from the beginnings of the files, and simultaneously from the ends,
+ doing a breadth-first search through the space of edit-sequence.
+ When the two searches meet, we have found the midpoint of the shortest
+ edit sequence.
+
+ The value returned is the number of the diagonal on which the midpoint lies.
+ The diagonal number equals the number of inserted lines minus the number
+ of deleted lines (counting only lines before the midpoint).
+ The edit cost is stored into COST; this is the total number of
+ lines inserted or deleted (counting only lines before the midpoint).
+
+ This function assumes that the first lines of the specified portions
+ of the two files do not match, and likewise that the last lines do not
+ match. The caller must trim matching lines from the beginning and end
+ of the portions it is going to specify.
+
+ Note that if we return the "wrong" diagonal value, or if
+ the value of bdiag at that diagonal is "wrong",
+ the worst this can do is cause suboptimal diff output.
+ It cannot cause incorrect diff output. */
+
+ private int diag (int xoff, int xlim, int yoff, int ylim) {
+ final int[] fd = fdiag; // Give the compiler a chance.
+ final int[] bd = bdiag; // Additional help for the compiler.
+ final int[] xv = xvec; // Still more help for the compiler.
+ final int[] yv = yvec; // And more and more . . .
+ final int dmin = xoff - ylim; // Minimum valid diagonal.
+ final int dmax = xlim - yoff; // Maximum valid diagonal.
+ final int fmid = xoff - yoff; // Center diagonal of top-down search.
+ final int bmid = xlim - ylim; // Center diagonal of bottom-up search.
+ int fmin = fmid, fmax = fmid; // Limits of top-down search.
+ int bmin = bmid, bmax = bmid; // Limits of bottom-up search.
+ /* True if southeast corner is on an odd
+ diagonal with respect to the northwest. */
+ final boolean odd = (fmid - bmid & 1) != 0;
+
+ fd[fdiagoff + fmid] = xoff;
+ bd[bdiagoff + bmid] = xlim;
+
+ for (int c = 1;; ++c)
+ {
+ int d; /* Active diagonal. */
+ boolean big_snake = false;
+
+ /* Extend the top-down search by an edit step in each diagonal. */
+ if (fmin > dmin)
+ fd[fdiagoff + --fmin - 1] = -1;
+ else
+ ++fmin;
+ if (fmax < dmax)
+ fd[fdiagoff + ++fmax + 1] = -1;
+ else
+ --fmax;
+ for (d = fmax; d >= fmin; d -= 2)
+ {
+ int x, y, oldx, tlo = fd[fdiagoff + d - 1], thi = fd[fdiagoff + d + 1];
+
+ if (tlo >= thi)
+ x = tlo + 1;
+ else
+ x = thi;
+ oldx = x;
+ y = x - d;
+ while (x < xlim && y < ylim && xv[x] == yv[y]) {
+ ++x; ++y;
+ }
+ if (x - oldx > 20)
+ big_snake = true;
+ fd[fdiagoff + d] = x;
+ if (odd && bmin <= d && d <= bmax && bd[bdiagoff + d] <= fd[fdiagoff + d])
+ {
+ cost = 2 * c - 1;
+ return d;
+ }
+ }
+
+ /* Similar extend the bottom-up search. */
+ if (bmin > dmin)
+ bd[bdiagoff + --bmin - 1] = Integer.MAX_VALUE;
+ else
+ ++bmin;
+ if (bmax < dmax)
+ bd[bdiagoff + ++bmax + 1] = Integer.MAX_VALUE;
+ else
+ --bmax;
+ for (d = bmax; d >= bmin; d -= 2)
+ {
+ int x, y, oldx, tlo = bd[bdiagoff + d - 1], thi = bd[bdiagoff + d + 1];
+
+ if (tlo < thi)
+ x = tlo;
+ else
+ x = thi - 1;
+ oldx = x;
+ y = x - d;
+ while (x > xoff && y > yoff && xv[x - 1] == yv[y - 1]) {
+ --x; --y;
+ }
+ if (oldx - x > 20)
+ big_snake = true;
+ bd[bdiagoff + d] = x;
+ if (!odd && fmin <= d && d <= fmax && bd[bdiagoff + d] <= fd[fdiagoff + d])
+ {
+ cost = 2 * c;
+ return d;
+ }
+ }
+
+ /* Heuristic: check occasionally for a diagonal that has made
+ lots of progress compared with the edit distance.
+ If we have any such, find the one that has made the most
+ progress and return it as if it had succeeded.
+
+ With this heuristic, for files with a constant small density
+ of changes, the algorithm is linear in the file size. */
+
+ if (c > 200 && big_snake && heuristic)
+ {
+ int best = 0;
+ int bestpos = -1;
+
+ for (d = fmax; d >= fmin; d -= 2)
+ {
+ int dd = d - fmid;
+ if ((fd[fdiagoff + d] - xoff)*2 - dd > 12 * (c + (dd > 0 ? dd : -dd)))
+ {
+ if (fd[fdiagoff + d] * 2 - dd > best
+ && fd[fdiagoff + d] - xoff > 20
+ && fd[fdiagoff + d] - d - yoff > 20)
+ {
+ int k;
+ int x = fd[fdiagoff + d];
+
+ /* We have a good enough best diagonal;
+ now insist that it end with a significant snake. */
+ for (k = 1; k <= 20; k++)
+ if (xvec[x - k] != yvec[x - d - k])
+ break;
+
+ if (k == 21)
+ {
+ best = fd[fdiagoff + d] * 2 - dd;
+ bestpos = d;
+ }
+ }
+ }
+ }
+ if (best > 0)
+ {
+ cost = 2 * c - 1;
+ return bestpos;
+ }
+
+ best = 0;
+ for (d = bmax; d >= bmin; d -= 2)
+ {
+ int dd = d - bmid;
+ if ((xlim - bd[bdiagoff + d])*2 + dd > 12 * (c + (dd > 0 ? dd : -dd)))
+ {
+ if ((xlim - bd[bdiagoff + d]) * 2 + dd > best
+ && xlim - bd[bdiagoff + d] > 20
+ && ylim - (bd[bdiagoff + d] - d) > 20)
+ {
+ /* We have a good enough best diagonal;
+ now insist that it end with a significant snake. */
+ int k;
+ int x = bd[bdiagoff + d];
+
+ for (k = 0; k < 20; k++)
+ if (xvec[x + k] != yvec[x - d + k])
+ break;
+ if (k == 20)
+ {
+ best = (xlim - bd[bdiagoff + d]) * 2 + dd;
+ bestpos = d;
+ }
+ }
+ }
+ }
+ if (best > 0)
+ {
+ cost = 2 * c - 1;
+ return bestpos;
+ }
+ }
+ }
+ }
+
+ /** Compare in detail contiguous subsequences of the two files
+ which are known, as a whole, to match each other.
+
+ The results are recorded in the IndexedSeqs filevec[N].changed_flag, by
+ storing a 1 in the element for each line that is an insertion or deletion.
+
+ The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
+
+ Note that XLIM, YLIM are exclusive bounds.
+ All line numbers are origin-0 and discarded lines are not counted. */
+
+ private void compareseq (int xoff, int xlim, int yoff, int ylim) {
+ /* Slide down the bottom initial diagonal. */
+ while (xoff < xlim && yoff < ylim && xvec[xoff] == yvec[yoff]) {
+ ++xoff; ++yoff;
+ }
+ /* Slide up the top initial diagonal. */
+ while (xlim > xoff && ylim > yoff && xvec[xlim - 1] == yvec[ylim - 1]) {
+ --xlim; --ylim;
+ }
+
+ /* Handle simple cases. */
+ if (xoff == xlim)
+ while (yoff < ylim)
+ filevec[1].changed_flag[1+filevec[1].realindexes[yoff++]] = true;
+ else if (yoff == ylim)
+ while (xoff < xlim)
+ filevec[0].changed_flag[1+filevec[0].realindexes[xoff++]] = true;
+ else
+ {
+ /* Find a point of correspondence in the middle of the files. */
+
+ int d = diag (xoff, xlim, yoff, ylim);
+ int c = cost;
+ int f = fdiag[fdiagoff + d];
+ int b = bdiag[bdiagoff + d];
+
+ if (c == 1)
+ {
+ /* This should be impossible, because it implies that
+ one of the two subsequences is empty,
+ and that case was handled above without calling `diag'.
+ Let's verify that this is true. */
+ throw new IllegalArgumentException("Empty subsequence");
+ }
+ else
+ {
+ /* Use that point to split this problem into two subproblems. */
+ compareseq (xoff, b, yoff, b - d);
+ /* This used to use f instead of b,
+ but that is incorrect!
+ It is not necessarily the case that diagonal d
+ has a snake from b to f. */
+ compareseq (b, xlim, b - d, ylim);
+ }
+ }
+ }
+
+ /** Discard lines from one file that have no matches in the other file.
+ */
+
+ private void discard_confusing_lines() {
+ filevec[0].discard_confusing_lines(filevec[1]);
+ filevec[1].discard_confusing_lines(filevec[0]);
+ }
+
+ private boolean inhibit = false;
+
+ /** Adjust inserts/deletes of blank lines to join changes
+ as much as possible.
+ */
+
+ private void shift_boundaries() {
+ if (inhibit)
+ return;
+ filevec[0].shift_boundaries(filevec[1]);
+ filevec[1].shift_boundaries(filevec[0]);
+ }
+
+ public interface ScriptBuilder {
+ /** Scan the tables of which lines are inserted and deleted,
+ producing an edit script.
+ @param changed0 true for lines in first file which do not match 2nd
+ @param len0 number of lines in first file
+ @param changed1 true for lines in 2nd file which do not match 1st
+ @param len1 number of lines in 2nd file
+ @return a linked list of changes - or null
+ */
+ public change build_script(
+ boolean[] changed0,int len0,
+ boolean[] changed1,int len1
+ );
+ }
+
+ /** Scan the tables of which lines are inserted and deleted,
+ producing an edit script in reverse order. */
+
+ static class ReverseScript implements ScriptBuilder {
+ public change build_script(
+ final boolean[] changed0,int len0,
+ final boolean[] changed1,int len1)
+ {
+ change script = null;
+ int i0 = 0, i1 = 0;
+ while (i0 < len0 || i1 < len1) {
+ if (changed0[1+i0] || changed1[1+i1]) {
+ int line0 = i0, line1 = i1;
+
+ /* Find # lines changed here in each file. */
+ while (changed0[1+i0]) ++i0;
+ while (changed1[1+i1]) ++i1;
+
+ /* Record this change. */
+ script = new change(line0, line1, i0 - line0, i1 - line1, script);
+ }
+
+ /* We have reached lines in the two files that match each other. */
+ i0++; i1++;
+ }
+
+ return script;
+ }
+ }
+
+ static class ForwardScript implements ScriptBuilder {
+ /** Scan the tables of which lines are inserted and deleted,
+ producing an edit script in forward order. */
+ public change build_script(
+ final boolean[] changed0,int len0,
+ final boolean[] changed1,int len1)
+ {
+ change script = null;
+ int i0 = len0, i1 = len1;
+
+ while (i0 >= 0 || i1 >= 0)
+ {
+ if (changed0[i0] || changed1[i1])
+ {
+ int line0 = i0, line1 = i1;
+
+ /* Find # lines changed here in each file. */
+ while (changed0[i0]) --i0;
+ while (changed1[i1]) --i1;
+
+ /* Record this change. */
+ script = new change(i0, i1, line0 - i0, line1 - i1, script);
+ }
+
+ /* We have reached lines in the two files that match each other. */
+ i0--; i1--;
+ }
+
+ return script;
+ }
+ }
+
+ /** Standard ScriptBuilders. */
+ public final static ScriptBuilder
+ forwardScript = new ForwardScript(),
+ reverseScript = new ReverseScript();
+
+ /* Report the differences of two files. DEPTH is the current directory
+ depth. */
+ public final change diff_2(final boolean reverse) {
+ return diff(reverse ? reverseScript : forwardScript);
+ }
+
+ /** Get the results of comparison as an edit script. The script
+ is described by a list of changes. The standard ScriptBuilder
+ implementations provide for forward and reverse edit scripts.
+ Alternate implementations could, for instance, list common elements
+ instead of differences.
+ @param bld an object to build the script from change flags
+ @return the head of a list of changes
+ */
+ public change diff(final ScriptBuilder bld) {
+
+ /* Some lines are obviously insertions or deletions
+ because they don't match anything. Detect them now,
+ and avoid even thinking about them in the main comparison algorithm. */
+
+ discard_confusing_lines ();
+
+ /* Now do the main comparison algorithm, considering just the
+ undiscarded lines. */
+
+ xvec = filevec[0].undiscarded;
+ yvec = filevec[1].undiscarded;
+
+ int diags =
+ filevec[0].nondiscarded_lines + filevec[1].nondiscarded_lines + 3;
+ fdiag = new int[diags];
+ fdiagoff = filevec[1].nondiscarded_lines + 1;
+ bdiag = new int[diags];
+ bdiagoff = filevec[1].nondiscarded_lines + 1;
+
+ compareseq (0, filevec[0].nondiscarded_lines,
+ 0, filevec[1].nondiscarded_lines);
+ fdiag = null;
+ bdiag = null;
+
+ /* Modify the results slightly to make them prettier
+ in cases where that can validly be done. */
+
+ shift_boundaries ();
+
+ /* Get the results of comparison in the form of a chain
+ of `struct change's -- an edit script. */
+ return bld.build_script(
+ filevec[0].changed_flag,
+ filevec[0].buffered_lines,
+ filevec[1].changed_flag,
+ filevec[1].buffered_lines
+ );
+
+ }
+
+ /** The result of comparison is an "edit script": a chain of change objects.
+ Each change represents one place where some lines are deleted
+ and some are inserted.
+
+ LINE0 and LINE1 are the first affected lines in the two files (origin 0).
+ DELETED is the number of lines deleted here from file 0.
+ INSERTED is the number of lines inserted here in file 1.
+
+ If DELETED is 0 then LINE0 is the number of the line before
+ which the insertion was done; vice versa for INSERTED and LINE1. */
+
+ public static class change {
+ /** Previous or next edit command. */
+ public change link;
+ /** # lines of file 1 changed here. */
+ public final int inserted;
+ /** # lines of file 0 changed here. */
+ public final int deleted;
+ /** Line number of 1st deleted line. */
+ public final int line0;
+ /** Line number of 1st inserted line. */
+ public final int line1;
+
+ /** Cons an additional entry onto the front of an edit script OLD.
+ LINE0 and LINE1 are the first affected lines in the two files (origin 0).
+ DELETED is the number of lines deleted here from file 0.
+ INSERTED is the number of lines inserted here in file 1.
+
+ If DELETED is 0 then LINE0 is the number of the line before
+ which the insertion was done; vice versa for INSERTED and LINE1. */
+ public change(int line0, int line1, int deleted, int inserted, change old) {
+ this.line0 = line0;
+ this.line1 = line1;
+ this.inserted = inserted;
+ this.deleted = deleted;
+ this.link = old;
+ //System.err.println(line0+","+line1+","+inserted+","+deleted);
+ }
+ }
+
+ /** Data on one input file being compared.
+ */
+
+ class file_data {
+
+ /** Allocate changed array for the results of comparison. */
+ void clear() {
+ /* Allocate a flag for each line of each file, saying whether that line
+ is an insertion or deletion.
+ Allocate an extra element, always zero, at each end of each IndexedSeq.
+ */
+ changed_flag = new boolean[buffered_lines + 2];
+ }
+
+ /** Return equiv_count[I] as the number of lines in this file
+ that fall in equivalence class I.
+ @return the array of equivalence class counts.
+ */
+ int[] equivCount() {
+ int[] equiv_count = new int[equiv_max];
+ for (int i = 0; i < buffered_lines; ++i)
+ ++equiv_count[equivs[i]];
+ return equiv_count;
+ }
+
+ /** Discard lines that have no matches in another file.
+
+ A line which is discarded will not be considered by the actual
+ comparison algorithm; it will be as if that line were not in the file.
+ The file's `realindexes' table maps virtual line numbers
+ (which don't count the discarded lines) into real line numbers;
+ this is how the actual comparison algorithm produces results
+ that are comprehensible when the discarded lines are counted.
+<p>
+ When we discard a line, we also mark it as a deletion or insertion
+ so that it will be printed in the output.
+ @param f the other file
+ */
+ void discard_confusing_lines(file_data f) {
+ clear();
+ /* Set up table of which lines are going to be discarded. */
+ final byte[] discarded = discardable(f.equivCount());
+
+ /* Don't really discard the provisional lines except when they occur
+ in a run of discardables, with nonprovisionals at the beginning
+ and end. */
+ filterDiscards(discarded);
+
+ /* Actually discard the lines. */
+ discard(discarded);
+ }
+
+ /** Mark to be discarded each line that matches no line of another file.
+ If a line matches many lines, mark it as provisionally discardable.
+ @see equivCount()
+ @param counts The count of each equivalence number for the other file.
+ @return 0=nondiscardable, 1=discardable or 2=provisionally discardable
+ for each line
+ */
+
+ private byte[] discardable(final int[] counts) {
+ final int end = buffered_lines;
+ final byte[] discards = new byte[end];
+ final int[] equivs = this.equivs;
+ int many = 5;
+ int tem = end / 64;
+
+ /* Multiply MANY by approximate square root of number of lines.
+ That is the threshold for provisionally discardable lines. */
+ while ((tem = tem >> 2) > 0)
+ many *= 2;
+
+ for (int i = 0; i < end; i++)
+ {
+ int nmatch;
+ if (equivs[i] == 0)
+ continue;
+ nmatch = counts[equivs[i]];
+ if (nmatch == 0)
+ discards[i] = 1;
+ else if (nmatch > many)
+ discards[i] = 2;
+ }
+ return discards;
+ }
+
+ /** Don't really discard the provisional lines except when they occur
+ in a run of discardables, with nonprovisionals at the beginning
+ and end. */
+
+ private void filterDiscards(final byte[] discards) {
+ final int end = buffered_lines;
+
+ for (int i = 0; i < end; i++)
+ {
+ /* Cancel provisional discards not in middle of run of discards. */
+ if (discards[i] == 2)
+ discards[i] = 0;
+ else if (discards[i] != 0)
+ {
+ /* We have found a nonprovisional discard. */
+ int j;
+ int length;
+ int provisional = 0;
+
+ /* Find end of this run of discardable lines.
+ Count how many are provisionally discardable. */
+ for (j = i; j < end; j++)
+ {
+ if (discards[j] == 0)
+ break;
+ if (discards[j] == 2)
+ ++provisional;
+ }
+
+ /* Cancel provisional discards at end, and shrink the run. */
+ while (j > i && discards[j - 1] == 2) {
+ discards[--j] = 0; --provisional;
+ }
+
+ /* Now we have the length of a run of discardable lines
+ whose first and last are not provisional. */
+ length = j - i;
+
+ /* If 1/4 of the lines in the run are provisional,
+ cancel discarding of all provisional lines in the run. */
+ if (provisional * 4 > length)
+ {
+ while (j > i)
+ if (discards[--j] == 2)
+ discards[j] = 0;
+ }
+ else
+ {
+ int consec;
+ int minimum = 1;
+ int tem = length / 4;
+
+ /* MINIMUM is approximate square root of LENGTH/4.
+ A subrun of two or more provisionals can stand
+ when LENGTH is at least 16.
+ A subrun of 4 or more can stand when LENGTH >= 64. */
+ while ((tem = tem >> 2) > 0)
+ minimum *= 2;
+ minimum++;
+
+ /* Cancel any subrun of MINIMUM or more provisionals
+ within the larger run. */
+ for (j = 0, consec = 0; j < length; j++)
+ if (discards[i + j] != 2)
+ consec = 0;
+ else if (minimum == ++consec)
+ /* Back up to start of subrun, to cancel it all. */
+ j -= consec;
+ else if (minimum < consec)
+ discards[i + j] = 0;
+
+ /* Scan from beginning of run
+ until we find 3 or more nonprovisionals in a row
+ or until the first nonprovisional at least 8 lines in.
+ Until that point, cancel any provisionals. */
+ for (j = 0, consec = 0; j < length; j++)
+ {
+ if (j >= 8 && discards[i + j] == 1)
+ break;
+ if (discards[i + j] == 2) {
+ consec = 0; discards[i + j] = 0;
+ }
+ else if (discards[i + j] == 0)
+ consec = 0;
+ else
+ consec++;
+ if (consec == 3)
+ break;
+ }
+
+ /* I advances to the last line of the run. */
+ i += length - 1;
+
+ /* Same thing, from end. */
+ for (j = 0, consec = 0; j < length; j++)
+ {
+ if (j >= 8 && discards[i - j] == 1)
+ break;
+ if (discards[i - j] == 2) {
+ consec = 0; discards[i - j] = 0;
+ }
+ else if (discards[i - j] == 0)
+ consec = 0;
+ else
+ consec++;
+ if (consec == 3)
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /** Actually discard the lines.
+ @param discards flags lines to be discarded
+ */
+ private void discard(final byte[] discards) {
+ final int end = buffered_lines;
+ int j = 0;
+ for (int i = 0; i < end; ++i)
+ if (no_discards || discards[i] == 0)
+ {
+ undiscarded[j] = equivs[i];
+ realindexes[j++] = i;
+ }
+ else
+ changed_flag[1+i] = true;
+ nondiscarded_lines = j;
+ }
+
+ file_data(Object[] data,Hashtable h) {
+ buffered_lines = data.length;
+
+ equivs = new int[buffered_lines];
+ undiscarded = new int[buffered_lines];
+ realindexes = new int[buffered_lines];
+
+ for (int i = 0; i < data.length; ++i) {
+ Integer ir = (Integer)h.get(data[i]);
+ if (ir == null)
+ h.put(data[i],new Integer(equivs[i] = equiv_max++));
+ else
+ equivs[i] = ir.intValue();
+ }
+ }
+
+ /** Adjust inserts/deletes of blank lines to join changes
+ as much as possible.
+
+ We do something when a run of changed lines include a blank
+ line at one end and have an excluded blank line at the other.
+ We are free to choose which blank line is included.
+ `compareseq' always chooses the one at the beginning,
+ but usually it is cleaner to consider the following blank line
+ to be the "change". The only exception is if the preceding blank line
+ would join this change to other changes.
+ @param f the file being compared against
+ */
+
+ void shift_boundaries(file_data f) {
+ final boolean[] changed = changed_flag;
+ final boolean[] other_changed = f.changed_flag;
+ int i = 0;
+ int j = 0;
+ int i_end = buffered_lines;
+ int preceding = -1;
+ int other_preceding = -1;
+
+ for (;;)
+ {
+ int start, end, other_start;
+
+ /* Scan forwards to find beginning of another run of changes.
+ Also keep track of the corresponding point in the other file. */
+
+ while (i < i_end && !changed[1+i])
+ {
+ while (other_changed[1+j++])
+ /* Non-corresponding lines in the other file
+ will count as the preceding batch of changes. */
+ other_preceding = j;
+ i++;
+ }
+
+ if (i == i_end)
+ break;
+
+ start = i;
+ other_start = j;
+
+ for (;;)
+ {
+ /* Now find the end of this run of changes. */
+
+ while (i < i_end && changed[1+i]) i++;
+ end = i;
+
+ /* If the first changed line matches the following unchanged one,
+ and this run does not follow right after a previous run,
+ and there are no lines deleted from the other file here,
+ then classify the first changed line as unchanged
+ and the following line as changed in its place. */
+
+ /* You might ask, how could this run follow right after another?
+ Only because the previous run was shifted here. */
+
+ if (end != i_end
+ && equivs[start] == equivs[end]
+ && !other_changed[1+j]
+ && end != i_end
+ && !((preceding >= 0 && start == preceding)
+ || (other_preceding >= 0
+ && other_start == other_preceding)))
+ {
+ changed[1+end++] = true;
+ changed[1+start++] = false;
+ ++i;
+ /* Since one line-that-matches is now before this run
+ instead of after, we must advance in the other file
+ to keep in synch. */
+ ++j;
+ }
+ else
+ break;
+ }
+
+ preceding = i;
+ other_preceding = j;
+ }
+ }
+
+ /** Number of elements (lines) in this file. */
+ final int buffered_lines;
+
+ /** IndexedSeq, indexed by line number, containing an equivalence code for
+ each line. It is this IndexedSeq that is actually compared with that
+ of another file to generate differences. */
+ private final int[] equivs;
+
+ /** IndexedSeq, like the previous one except that
+ the elements for discarded lines have been squeezed out. */
+ final int[] undiscarded;
+
+ /** IndexedSeq mapping virtual line numbers (not counting discarded lines)
+ to real ones (counting those lines). Both are origin-0. */
+ final int[] realindexes;
+
+ /** Total number of nondiscarded lines. */
+ int nondiscarded_lines;
+
+ /** Array, indexed by real origin-1 line number,
+ containing true for a line that is an insertion or a deletion.
+ The results of comparison are stored here. */
+ boolean[] changed_flag;
+
+ }
+}
diff --git a/src/partest-alternative/scala/tools/partest/io/DiffPrint.java b/src/partest-alternative/scala/tools/partest/io/DiffPrint.java
new file mode 100644
index 0000000000..2b2ad93ec7
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/io/DiffPrint.java
@@ -0,0 +1,607 @@
+// $Id$
+
+package scala.tools.partest.io;
+
+import java.io.*;
+import java.util.Vector;
+import java.util.Date;
+//import com.objectspace.jgl.predicates.UnaryPredicate;
+
+interface UnaryPredicate {
+ boolean execute(Object obj);
+}
+
+/** A simple framework for printing change lists produced by <code>Diff</code>.
+ @see bmsi.util.Diff
+ @author Stuart D. Gathman
+ Copyright (C) 2000 Business Management Systems, Inc.
+<p>
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 1, or (at your option)
+ any later version.
+<p>
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+<p>
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+public class DiffPrint {
+ /** A Base class for printing edit scripts produced by Diff.
+ This class divides the change list into "hunks", and calls
+ <code>print_hunk</code> for each hunk. Various utility methods
+ are provided as well.
+ */
+ public static abstract class Base {
+ protected Base(Object[] a,Object[] b, Writer w) {
+ outfile = new PrintWriter(w);
+ file0 = a;
+ file1 = b;
+ }
+ /** Set to ignore certain kinds of lines when printing
+ an edit script. For example, ignoring blank lines or comments.
+ */
+ protected UnaryPredicate ignore = null;
+
+ /** Set to the lines of the files being compared.
+ */
+ protected Object[] file0, file1;
+
+ /** Divide SCRIPT into pieces by calling HUNKFUN and
+ print each piece with PRINTFUN.
+ Both functions take one arg, an edit script.
+
+ PRINTFUN takes a subscript which belongs together (with a null
+ link at the end) and prints it. */
+ public void print_script(Diff.change script) {
+ Diff.change next = script;
+
+ while (next != null)
+ {
+ Diff.change t, end;
+
+ /* Find a set of changes that belong together. */
+ t = next;
+ end = hunkfun(next);
+
+ /* Disconnect them from the rest of the changes,
+ making them a hunk, and remember the rest for next iteration. */
+ next = end.link;
+ end.link = null;
+ //if (DEBUG)
+ // debug_script(t);
+
+ /* Print this hunk. */
+ print_hunk(t);
+
+ /* Reconnect the script so it will all be freed properly. */
+ end.link = next;
+ }
+ outfile.flush();
+ }
+
+ /** Called with the tail of the script
+ and returns the last link that belongs together with the start
+ of the tail. */
+
+ protected Diff.change hunkfun(Diff.change hunk) {
+ return hunk;
+ }
+
+ protected int first0, last0, first1, last1, deletes, inserts;
+ protected PrintWriter outfile;
+
+ /** Look at a hunk of edit script and report the range of lines in each file
+ that it applies to. HUNK is the start of the hunk, which is a chain
+ of `struct change'. The first and last line numbers of file 0 are stored
+ in *FIRST0 and *LAST0, and likewise for file 1 in *FIRST1 and *LAST1.
+ Note that these are internal line numbers that count from 0.
+
+ If no lines from file 0 are deleted, then FIRST0 is LAST0+1.
+
+ Also set *DELETES nonzero if any lines of file 0 are deleted
+ and set *INSERTS nonzero if any lines of file 1 are inserted.
+ If only ignorable lines are inserted or deleted, both are
+ set to 0. */
+
+ protected void analyze_hunk(Diff.change hunk) {
+ int f0, l0 = 0, f1, l1 = 0, show_from = 0, show_to = 0;
+ int i;
+ Diff.change next;
+ boolean nontrivial = (ignore == null);
+
+ show_from = show_to = 0;
+
+ f0 = hunk.line0;
+ f1 = hunk.line1;
+
+ for (next = hunk; next != null; next = next.link)
+ {
+ l0 = next.line0 + next.deleted - 1;
+ l1 = next.line1 + next.inserted - 1;
+ show_from += next.deleted;
+ show_to += next.inserted;
+ for (i = next.line0; i <= l0 && ! nontrivial; i++)
+ if (!ignore.execute(file0[i]))
+ nontrivial = true;
+ for (i = next.line1; i <= l1 && ! nontrivial; i++)
+ if (!ignore.execute(file1[i]))
+ nontrivial = true;
+ }
+
+ first0 = f0;
+ last0 = l0;
+ first1 = f1;
+ last1 = l1;
+
+ /* If all inserted or deleted lines are ignorable,
+ tell the caller to ignore this hunk. */
+
+ if (!nontrivial)
+ show_from = show_to = 0;
+
+ deletes = show_from;
+ inserts = show_to;
+ }
+
+ /** Print the script header which identifies the files compared. */
+ protected void print_header(String filea, String fileb) { }
+
+ protected abstract void print_hunk(Diff.change hunk);
+
+ protected void print_1_line(String pre,Object linbuf) {
+ outfile.println(pre + linbuf.toString());
+ }
+
+ /** Print a pair of line numbers with SEPCHAR, translated for file FILE.
+ If the two numbers are identical, print just one number.
+
+ Args A and B are internal line numbers.
+ We print the translated (real) line numbers. */
+
+ protected void print_number_range (char sepchar, int a, int b) {
+ /* Note: we can have B < A in the case of a range of no lines.
+ In this case, we should print the line number before the range,
+ which is B. */
+ if (++b > ++a)
+ outfile.print("" + a + sepchar + b);
+ else
+ outfile.print(b);
+ }
+
+ public static char change_letter(int inserts, int deletes) {
+ if (inserts == 0)
+ return 'd';
+ else if (deletes == 0)
+ return 'a';
+ else
+ return 'c';
+ }
+ }
+
+ /** Print a change list in the standard diff format.
+ */
+ public static class NormalPrint extends Base {
+
+ public NormalPrint(Object[] a,Object[] b, Writer w) {
+ super(a,b,w);
+ }
+
+ /** Print a hunk of a normal diff.
+ This is a contiguous portion of a complete edit script,
+ describing changes in consecutive lines. */
+
+ protected void print_hunk (Diff.change hunk) {
+
+ /* Determine range of line numbers involved in each file. */
+ analyze_hunk(hunk);
+ if (deletes == 0 && inserts == 0)
+ return;
+
+ /* Print out the line number header for this hunk */
+ print_number_range (',', first0, last0);
+ outfile.print(change_letter(inserts, deletes));
+ print_number_range (',', first1, last1);
+ outfile.println();
+
+ /* Print the lines that the first file has. */
+ if (deletes != 0)
+ for (int i = first0; i <= last0; i++)
+ print_1_line ("< ", file0[i]);
+
+ if (inserts != 0 && deletes != 0)
+ outfile.println("---");
+
+ /* Print the lines that the second file has. */
+ if (inserts != 0)
+ for (int i = first1; i <= last1; i++)
+ print_1_line ("> ", file1[i]);
+ }
+ }
+
+ /** Prints an edit script in a format suitable for input to <code>ed</code>.
+ The edit script must be generated with the reverse option to
+ be useful as actual <code>ed</code> input.
+ */
+ public static class EdPrint extends Base {
+
+ public EdPrint(Object[] a,Object[] b, Writer w) {
+ super(a,b,w);
+ }
+
+ /** Print a hunk of an ed diff */
+ protected void print_hunk(Diff.change hunk) {
+
+ /* Determine range of line numbers involved in each file. */
+ analyze_hunk (hunk);
+ if (deletes == 0 && inserts == 0)
+ return;
+
+ /* Print out the line number header for this hunk */
+ print_number_range (',', first0, last0);
+ outfile.println(change_letter(inserts, deletes));
+
+ /* Print new/changed lines from second file, if needed */
+ if (inserts != 0)
+ {
+ boolean inserting = true;
+ for (int i = first1; i <= last1; i++)
+ {
+ /* Resume the insert, if we stopped. */
+ if (! inserting)
+ outfile.println(i - first1 + first0 + "a");
+ inserting = true;
+
+ /* If the file's line is just a dot, it would confuse `ed'.
+ So output it with a double dot, and set the flag LEADING_DOT
+ so that we will output another ed-command later
+ to change the double dot into a single dot. */
+
+ if (".".equals(file1[i]))
+ {
+ outfile.println("..");
+ outfile.println(".");
+ /* Now change that double dot to the desired single dot. */
+ outfile.println(i - first1 + first0 + 1 + "s/^\\.\\././");
+ inserting = false;
+ }
+ else
+ /* Line is not `.', so output it unmodified. */
+ print_1_line ("", file1[i]);
+ }
+
+ /* End insert mode, if we are still in it. */
+ if (inserting)
+ outfile.println(".");
+ }
+ }
+ }
+
+ /** Prints an edit script in context diff format. This and its
+ 'unified' variation is used for source code patches.
+ */
+ public static class ContextPrint extends Base {
+
+ protected int context = 3;
+
+ public ContextPrint(Object[] a,Object[] b, Writer w) {
+ super(a,b,w);
+ }
+
+ protected void print_context_label (String mark, File inf, String label) {
+ if (label != null)
+ outfile.println(mark + ' ' + label);
+ else if (inf.lastModified() > 0)
+ // FIXME: use DateFormat to get precise format needed.
+ outfile.println(
+ mark + ' ' + inf.getPath() + '\t' + new Date(inf.lastModified())
+ );
+ else
+ /* Don't pretend that standard input is ancient. */
+ outfile.println(mark + ' ' + inf.getPath());
+ }
+
+ public void print_header(String filea,String fileb) {
+ print_context_label ("***", new File(filea), filea);
+ print_context_label ("---", new File(fileb), fileb);
+ }
+
+ /** If function_regexp defined, search for start of function. */
+ private String find_function(Object[] lines, int start) {
+ return null;
+ }
+
+ protected void print_function(Object[] file,int start) {
+ String function = find_function (file0, first0);
+ if (function != null) {
+ outfile.print(" ");
+ outfile.print(
+ (function.length() < 40) ? function : function.substring(0,40)
+ );
+ }
+ }
+
+ protected void print_hunk(Diff.change hunk) {
+
+ /* Determine range of line numbers involved in each file. */
+
+ analyze_hunk (hunk);
+
+ if (deletes == 0 && inserts == 0)
+ return;
+
+ /* Include a context's width before and after. */
+
+ first0 = Math.max(first0 - context, 0);
+ first1 = Math.max(first1 - context, 0);
+ last0 = Math.min(last0 + context, file0.length - 1);
+ last1 = Math.min(last1 + context, file1.length - 1);
+
+
+ outfile.print("***************");
+
+ /* If we looked for and found a function this is part of,
+ include its name in the header of the diff section. */
+ print_function (file0, first0);
+
+ outfile.println();
+ outfile.print("*** ");
+ print_number_range (',', first0, last0);
+ outfile.println(" ****");
+
+ if (deletes != 0) {
+ Diff.change next = hunk;
+
+ for (int i = first0; i <= last0; i++) {
+ /* Skip past changes that apply (in file 0)
+ only to lines before line I. */
+
+ while (next != null && next.line0 + next.deleted <= i)
+ next = next.link;
+
+ /* Compute the marking for line I. */
+
+ String prefix = " ";
+ if (next != null && next.line0 <= i)
+ /* The change NEXT covers this line.
+ If lines were inserted here in file 1, this is "changed".
+ Otherwise it is "deleted". */
+ prefix = (next.inserted > 0) ? "!" : "-";
+
+ print_1_line (prefix, file0[i]);
+ }
+ }
+
+ outfile.print("--- ");
+ print_number_range (',', first1, last1);
+ outfile.println(" ----");
+
+ if (inserts != 0) {
+ Diff.change next = hunk;
+
+ for (int i = first1; i <= last1; i++) {
+ /* Skip past changes that apply (in file 1)
+ only to lines before line I. */
+
+ while (next != null && next.line1 + next.inserted <= i)
+ next = next.link;
+
+ /* Compute the marking for line I. */
+
+ String prefix = " ";
+ if (next != null && next.line1 <= i)
+ /* The change NEXT covers this line.
+ If lines were deleted here in file 0, this is "changed".
+ Otherwise it is "inserted". */
+ prefix = (next.deleted > 0) ? "!" : "+";
+
+ print_1_line (prefix, file1[i]);
+ }
+ }
+ }
+ }
+
+ /** Prints an edit script in context diff format. This and its
+ 'unified' variation is used for source code patches.
+ */
+ public static class UnifiedPrint extends ContextPrint {
+
+ public UnifiedPrint(Object[] a,Object[] b, Writer w) {
+ super(a,b,w);
+ }
+
+ public void print_header(String filea,String fileb) {
+ print_context_label ("---", new File(filea), filea);
+ print_context_label ("+++", new File(fileb), fileb);
+ }
+
+ private void print_number_range (int a, int b) {
+ //translate_range (file, a, b, &trans_a, &trans_b);
+
+ /* Note: we can have B < A in the case of a range of no lines.
+ In this case, we should print the line number before the range,
+ which is B. */
+ if (b < a)
+ outfile.print(b + ",0");
+ else
+ super.print_number_range(',',a,b);
+ }
+
+ protected void print_hunk(Diff.change hunk) {
+ /* Determine range of line numbers involved in each file. */
+ analyze_hunk (hunk);
+
+ if (deletes == 0 && inserts == 0)
+ return;
+
+ /* Include a context's width before and after. */
+
+ first0 = Math.max(first0 - context, 0);
+ first1 = Math.max(first1 - context, 0);
+ last0 = Math.min(last0 + context, file0.length - 1);
+ last1 = Math.min(last1 + context, file1.length - 1);
+
+
+
+ outfile.print("@@ -");
+ print_number_range (first0, last0);
+ outfile.print(" +");
+ print_number_range (first1, last1);
+ outfile.print(" @@");
+
+ /* If we looked for and found a function this is part of,
+ include its name in the header of the diff section. */
+ print_function(file0,first0);
+
+ outfile.println();
+
+ Diff.change next = hunk;
+ int i = first0;
+ int j = first1;
+
+ while (i <= last0 || j <= last1) {
+
+ /* If the line isn't a difference, output the context from file 0. */
+
+ if (next == null || i < next.line0) {
+ outfile.print(' ');
+ print_1_line ("", file0[i++]);
+ j++;
+ }
+ else {
+ /* For each difference, first output the deleted part. */
+
+ int k = next.deleted;
+ while (k-- > 0) {
+ outfile.print('-');
+ print_1_line ("", file0[i++]);
+ }
+
+ /* Then output the inserted part. */
+
+ k = next.inserted;
+ while (k-- > 0) {
+ outfile.print('+');
+ print_1_line ("", file1[j++]);
+ }
+
+ /* We're done with this hunk, so on to the next! */
+
+ next = next.link;
+ }
+ }
+ }
+ }
+
+
+ /** Read a text file into an array of String. This provides basic diff
+ functionality. A more advanced diff utility will use specialized
+ objects to represent the text lines, with options to, for example,
+ convert sequences of whitespace to a single space for comparison
+ purposes.
+ */
+ static String[] slurp(String file) throws IOException {
+ BufferedReader rdr = new BufferedReader(new FileReader(file));
+ Vector s = new Vector();
+ for (;;) {
+ String line = rdr.readLine();
+ if (line == null) break;
+ s.addElement(line);
+ }
+ String[] a = new String[s.size()];
+ s.copyInto(a);
+ return a;
+ }
+
+ public static void main(String[] argv) throws IOException {
+ String filea = argv[argv.length - 2];
+ String fileb = argv[argv.length - 1];
+ String[] a = slurp(filea);
+ String[] b = slurp(fileb);
+ Diff d = new Diff(a,b);
+ char style = 'n';
+ for (int i = 0; i < argv.length - 2; ++i) {
+ String f = argv[i];
+ if (f.startsWith("-")) {
+ for (int j = 1; j < f.length(); ++j) {
+ switch (f.charAt(j)) {
+ case 'e': // Ed style
+ style = 'e'; break;
+ case 'c': // Context diff
+ style = 'c'; break;
+ case 'u':
+ style = 'u'; break;
+ }
+ }
+ }
+ }
+ boolean reverse = style == 'e';
+ Diff.change script = d.diff_2(reverse);
+ if (script == null)
+ System.err.println("No differences");
+ else {
+ Base p;
+ Writer w = new OutputStreamWriter(System.out);
+ switch (style) {
+ case 'e':
+ p = new EdPrint(a,b,w); break;
+ case 'c':
+ p = new ContextPrint(a,b,w); break;
+ case 'u':
+ p = new UnifiedPrint(a,b,w); break;
+ default:
+ p = new NormalPrint(a,b,w);
+ }
+ p.print_header(filea,fileb);
+ p.print_script(script);
+ }
+ }
+
+ public static void doDiff(String[] argv, Writer w) throws IOException {
+ String filea = argv[argv.length - 2];
+ String fileb = argv[argv.length - 1];
+ String[] a = slurp(filea);
+ String[] b = slurp(fileb);
+ Diff d = new Diff(a,b);
+ char style = 'n';
+ for (int i = 0; i < argv.length - 2; ++i) {
+ String f = argv[i];
+ if (f.startsWith("-")) {
+ for (int j = 1; j < f.length(); ++j) {
+ switch (f.charAt(j)) {
+ case 'e': // Ed style
+ style = 'e'; break;
+ case 'c': // Context diff
+ style = 'c'; break;
+ case 'u':
+ style = 'u'; break;
+ }
+ }
+ }
+ }
+ boolean reverse = style == 'e';
+ Diff.change script = d.diff_2(reverse);
+ if (script == null)
+ w.write("No differences\n");
+ else {
+ Base p;
+ switch (style) {
+ case 'e':
+ p = new EdPrint(a,b,w); break;
+ case 'c':
+ p = new ContextPrint(a,b,w); break;
+ case 'u':
+ p = new UnifiedPrint(a,b,w); break;
+ default:
+ p = new NormalPrint(a,b,w);
+ }
+ p.print_header(filea,fileb);
+ p.print_script(script);
+ }
+ }
+
+}
diff --git a/src/partest-alternative/scala/tools/partest/io/JUnitReport.scala b/src/partest-alternative/scala/tools/partest/io/JUnitReport.scala
new file mode 100644
index 0000000000..63ae200020
--- /dev/null
+++ b/src/partest-alternative/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-alternative/scala/tools/partest/io/Logging.scala b/src/partest-alternative/scala/tools/partest/io/Logging.scala
new file mode 100644
index 0000000000..52239ffb2c
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/io/Logging.scala
@@ -0,0 +1,137 @@
+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()
+ }
+
+ /** What to print in a failure summary.
+ */
+ def failureMessage() = if (diffOutput != "") diffOutput else 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): String = if (isVerbose) path.path else path.name
+ def tracePath(path: String): String = tracePath(Path(path))
+
+ /** v == verbose.
+ */
+ def vtrace(msg: String) = if (isVerbose) trace(msg)
+
+ /** Run body, writes result to logFile. Any throwable is
+ * caught, stringified, and written to the log.
+ */
+ def loggingResult(body: => String) =
+ try returning(true)(_ => logFile writeAll body)
+ catch {
+ case x: ControlThrowable => throw x
+ case x: InterruptedException => debug(this + " received interrupt, failing.\n") ; false
+ case x: Throwable => logException(x)
+ }
+
+ def throwableToString(x: Throwable): String = {
+ val w = new StringWriter
+ x.printStackTrace(new PrintWriter(w))
+ w.toString
+ }
+
+ def warnAndLog(str: String) = {
+ warning(toStringTrunc(str, 800))
+ logWriter append str
+ }
+
+ def warnAndLogException(msg: String, ex: Throwable) =
+ warnAndLog(msg + throwableToString(ex))
+
+ 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-alternative/scala/tools/partest/nest/StreamAppender.scala b/src/partest-alternative/scala/tools/partest/nest/StreamAppender.scala
new file mode 100644
index 0000000000..8cebcf1685
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/nest/StreamAppender.scala
@@ -0,0 +1,94 @@
+/* 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-alternative/scala/tools/partest/package.scala b/src/partest-alternative/scala/tools/partest/package.scala
new file mode 100644
index 0000000000..f6d216e379
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/package.scala
@@ -0,0 +1,45 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2010 LAMP/EPFL
+ */
+
+package scala.tools
+
+import nsc.io.{ File, Path, Process, Directory }
+import java.nio.charset.CharacterCodingException
+
+package object partest {
+ /** The CharacterCodingExceptions are thrown at least on windows trying
+ * to read a file like script/utf-8.scala
+ */
+ private[partest] def safeSlurp(f: File) =
+ try if (f.exists) f.slurp() else ""
+ catch { case _: CharacterCodingException => "" }
+
+ private[partest] def safeLines(f: File) = safeSlurp(f) split """\r\n|\r|\n""" toList
+ private[partest] def safeArgs(f: File) = toArgs(safeSlurp(f))
+ private[partest] def isJava(f: Path) = f.isFile && (f hasExtension "java")
+ private[partest] def isScala(f: Path) = f.isFile && (f hasExtension "scala")
+ private[partest] def isJavaOrScala(f: Path) = isJava(f) || isScala(f)
+
+ private[partest] def toArgs(line: String) = cmd toArgs line
+ private[partest] def fromArgs(args: List[String]) = cmd fromArgs args
+
+ /** Strings, argument lists, etc. */
+
+ private[partest] def fromAnyArgs(args: List[Any]) = args mkString " " // separate to avoid accidents
+ private[partest] def toStringTrunc(x: Any, max: Int = 240) = {
+ val s = x.toString
+ if (s.length < max) s
+ else (s take max) + " [...]"
+ }
+ private[partest] def setProp(k: String, v: String) = scala.util.Properties.setProp(k, v)
+
+ /** Pretty self explanatory. */
+ def printAndExit(msg: String): Unit = {
+ println(msg)
+ exit(1)
+ }
+
+ /** Apply a function and return the passed value */
+ def returning[T](x: T)(f: T => Unit): T = { f(x) ; x }
+} \ No newline at end of file
diff --git a/src/partest-alternative/scala/tools/partest/util/package.scala b/src/partest-alternative/scala/tools/partest/util/package.scala
new file mode 100644
index 0000000000..bc5470ba5d
--- /dev/null
+++ b/src/partest-alternative/scala/tools/partest/util/package.scala
@@ -0,0 +1,61 @@
+/* NEST (New Scala Test)
+ * Copyright 2007-2010 LAMP/EPFL
+ */
+
+package scala.tools
+package partest
+
+import java.util.{ Timer, TimerTask }
+import java.io.StringWriter
+import nsc.io._
+
+/** Misc code still looking for a good home.
+ */
+package object util {
+
+ def allPropertiesString() = javaHashtableToString(System.getProperties)
+
+ private def javaHashtableToString(table: java.util.Hashtable[_,_]) = {
+ import collection.JavaConversions._
+ (table.toList map { case (k, v) => "%s -> %s\n".format(k, v) }).sorted mkString
+ }
+
+ def filesToSet(pre: String, fs: List[String]): Set[AbstractFile] =
+ fs flatMap (x => Option(AbstractFile getFile (Path(pre) / x).path)) toSet
+
+ /** Copies one Path to another Path, trying to be sensible when one or the
+ * other is a Directory. Returns true if it believes it succeeded.
+ */
+ def copyPath(from: Path, to: Path): Boolean = {
+ if (!to.parent.isDirectory)
+ to.parent.createDirectory(force = true)
+
+ def copyDir = {
+ val sub = to / from.name createDirectory true
+ from.toDirectory.list forall (x => copyPath(x, sub))
+ }
+ (from.isDirectory, to.isDirectory) match {
+ case (true, true) => copyDir
+ case (true, false) => false
+ case (false, true) => from.toFile copyTo (to / from.name)
+ case (false, false) => from.toFile copyTo to
+ }
+ }
+
+ /**
+ * Compares two files using a Java implementation of the GNU diff
+ * available at http://www.bmsi.com/java/#diff.
+ *
+ * @param f1 the first file to be compared
+ * @param f2 the second file to be compared
+ * @return the text difference between the compared files
+ */
+ def diffFiles(f1: File, f2: File): String = {
+ val diffWriter = new StringWriter
+ val args = Array(f1.toAbsolute.path, f2.toAbsolute.path)
+
+ io.DiffPrint.doDiff(args, diffWriter)
+ val result = diffWriter.toString
+ if (result == "No differences") "" else result
+ }
+}