diff options
Diffstat (limited to 'examples/scala-js/partest/src/main/scala/scala')
4 files changed, 651 insertions, 0 deletions
diff --git a/examples/scala-js/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala b/examples/scala-js/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala new file mode 100644 index 0000000..6857142 --- /dev/null +++ b/examples/scala-js/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala @@ -0,0 +1,220 @@ +package scala.tools.nsc + +/* Super hacky overriding of the MainGenericRunner used by partest */ + +import scala.scalajs.ir + +import scala.scalajs.tools.sem.Semantics +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.classpath.builder._ +import scala.scalajs.tools.logging._ +import scala.scalajs.tools.io._ +import scala.scalajs.tools.optimizer.ScalaJSOptimizer +import scala.scalajs.tools.optimizer.ScalaJSClosureOptimizer +import scala.scalajs.tools.optimizer.ParIncOptimizer +import scala.scalajs.tools.env.JSConsole + +import scala.scalajs.sbtplugin.env.rhino.RhinoJSEnv +import scala.scalajs.sbtplugin.env.nodejs.NodeJSEnv +import scala.scalajs.sbtplugin.JSUtils._ + +import scala.tools.partest.scalajs.ScalaJSPartestOptions._ + +import java.io.File +import scala.io.Source + +import Properties.{ versionString, copyrightString } +import GenericRunnerCommand._ + +class ScalaConsoleJSConsole extends JSConsole { + def log(msg: Any) = scala.Console.out.println(msg.toString) +} + +class MainGenericRunner { + def errorFn(ex: Throwable): Boolean = { + ex.printStackTrace() + false + } + def errorFn(str: String): Boolean = { + scala.Console.err println str + false + } + + val optMode = OptMode.fromId(sys.props("scalajs.partest.optMode")) + + def noWarnMissing = { + import ScalaJSOptimizer._ + + for { + fname <- sys.props.get("scalajs.partest.noWarnFile").toList + line <- Source.fromFile(fname).getLines + if !line.startsWith("#") + } yield line.split('.') match { + case Array(className) => NoWarnClass(className) + case Array(className, methodName) => NoWarnMethod(className, methodName) + } + } + + def readSemantics() = { + val opt = sys.props.get("scalajs.partest.compliantSems") + opt.fold(Semantics.Defaults) { str => + val sems = str.split(',') + Semantics.compliantTo(sems.toList) + } + } + + def process(args: Array[String]): Boolean = { + val command = new GenericRunnerCommand(args.toList, (x: String) => errorFn(x)) + import command.{ settings, howToRun, thingToRun } + + if (!command.ok) return errorFn("\n" + command.shortUsageMsg) + else if (settings.version) return errorFn("Scala code runner %s -- %s".format(versionString, copyrightString)) + else if (command.shouldStopWithInfo) return errorFn("shouldStopWithInfo") + + if (howToRun != AsObject) + return errorFn("Scala.js runner can only run an object") + + // Load basic Scala.js classpath (used for running or further packaging) + val usefulClasspathEntries = for { + url <- settings.classpathURLs + f = urlToFile(url) + if (f.isDirectory || f.getName.startsWith("scalajs-library")) + } yield f + val classpath = + PartialClasspathBuilder.build(usefulClasspathEntries).resolve() + + val logger = new ScalaConsoleLogger(Level.Warn) + val jsConsole = new ScalaConsoleJSConsole + + val mainObjName = ir.Definitions.encodeClassName(thingToRun) + val baseRunner = runnerJSFile(mainObjName, command.arguments) + val semantics = readSemantics() + + def fastOpted = fastOptimize(classpath, mainObjName, logger, semantics) + def fullOpted = fullOptimize(classpath, mainObjName, logger, + baseRunner, semantics.optimized) + + val runner = { + if (optMode == FullOpt) + fullOptRunner() + else + baseRunner + } + + val env = + if (optMode == NoOpt) new RhinoJSEnv(semantics) + else new NodeJSEnv + + val runClasspath = optMode match { + case NoOpt => classpath + case FastOpt => fastOpted + case FullOpt => fullOpted + } + + env.jsRunner(runClasspath, runner, logger, jsConsole).run() + + true + } + + private def runnerJSFile(mainObj: String, args: List[String]) = { + val jsObj = "ScalaJS.m." + mainObj + val jsArgs = argArray(args) + new MemVirtualJSFile("Generated launcher file"). + withContent(s"$jsObj().main__AT__V($jsArgs);") + } + + /** constructs a scala.Array[String] with the given elements */ + private def argArray(args: List[String]) = { + s"""ScalaJS.makeNativeArrayWrapper( + ScalaJS.d.T.getArrayOf(), + ${listToJS(args)})""" + } + + private def fastOptimize( + classpath: IRClasspath, + mainObjName: String, + logger: Logger, + semantics: Semantics) = { + import ScalaJSOptimizer._ + + val optimizer = newScalaJSOptimizer(semantics) + val output = WritableMemVirtualJSFile("partest fastOpt file") + + optimizer.optimizeCP( + Inputs(classpath, + manuallyReachable = fastOptReachable(mainObjName), + noWarnMissing = noWarnMissing + ), + OutputConfig( + output = output, + wantSourceMap = false, + checkIR = true + ), + logger) + } + + private def fastOptReachable(mainObjName: String) = { + import ScalaJSOptimizer._ + List( + ReachObject(mainObjName), + ReachMethod(mainObjName + '$', "main__AT__V", static = false) + ) + } + + private def fullOptimize( + classpath: IRClasspath, + mainObjName: String, + logger: Logger, + runner: VirtualJSFile, + semantics: Semantics) = { + import ScalaJSClosureOptimizer._ + + val fastOptimizer = newScalaJSOptimizer(semantics) + val fullOptimizer = new ScalaJSClosureOptimizer(semantics) + val output = WritableMemVirtualJSFile("partest fullOpt file") + val exportFile = fullOptExportFile(runner) + + fullOptimizer.optimizeCP(fastOptimizer, Inputs( + input = ScalaJSOptimizer.Inputs( + classpath, + manuallyReachable = fastOptReachable(mainObjName), + noWarnMissing = noWarnMissing), + additionalExports = exportFile :: Nil), + OutputConfig( + output, + checkIR = true, + wantSourceMap = false), + logger) + + } + + private def newScalaJSOptimizer(semantics: Semantics) = + new ScalaJSOptimizer(semantics, new ParIncOptimizer(_)) + + /** generates an exporter statement for the google closure compiler that runs + * what the normal test would + */ + private def fullOptExportFile(runnerFile: VirtualJSFile) = { + new MemVirtualJSFile("partest fullOpt exports").withContent( + s"""this["runFullOptPartest"] = function() { ${runnerFile.content} };""" + ) + } + + private def fullOptRunner() = new MemVirtualJSFile("partest fullOpt runner"). + withContent("runFullOptPartest();") + + private def urlToFile(url: java.net.URL) = { + try { + new File(url.toURI()) + } catch { + case e: java.net.URISyntaxException => new File(url.getPath()) + } + } +} + +object MainGenericRunner extends MainGenericRunner { + def main(args: Array[String]) { + if (!process(args)) + sys.exit(1) + } +} diff --git a/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/PartestInterface.scala b/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/PartestInterface.scala new file mode 100644 index 0000000..0dc2189 --- /dev/null +++ b/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/PartestInterface.scala @@ -0,0 +1,119 @@ +/* NOTE + * Most of this file is copy-pasted from + * https://github.com/scala/scala-partest-interface + * It is unfortunately not configurable enough, hence the duplication + */ + +package scala.tools.partest +package scalajs + +import scala.language.reflectiveCalls + +import sbt.testing.Fingerprint +import sbt.testing.TaskDef +import sbt.testing.EventHandler +import sbt.testing.Logger +import sbt.testing.Task +import sbt.testing.AnnotatedFingerprint +import java.net.URLClassLoader +import java.io.File + +object Framework { + // as partest is not driven by test classes discovered by sbt, need to add this marker fingerprint to definedTests + val fingerprint = new AnnotatedFingerprint { def isModule = true; def annotationName = "partest" } + + // TODO how can we export `fingerprint` so that a user can just add this to their build.sbt + // definedTests in Test += new sbt.TestDefinition("partest", fingerprint, true, Array()) +} +class Framework extends sbt.testing.Framework { + def fingerprints: Array[Fingerprint] = Array(Framework.fingerprint) + def name: String = "partest" + + def runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader): sbt.testing.Runner = + new Runner(args, remoteArgs, testClassLoader) +} + +/** Represents one run of a suite of tests. + */ +case class Runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader) extends sbt.testing.Runner { + /** Returns an array of tasks that when executed will run tests and suites determined by the + * passed <code>TaskDef</code>s. + * + * <p> + * Each returned task, when executed, will run tests and suites determined by the + * test class name, fingerprints, "explicitly specified" field, and selectors of one of the passed <code>TaskDef</code>s. + * </p> + * + * <p> + * This <code>tasks</code> method may be called with <code>TaskDef</code>s containing the same value for <code>testClassName</code> but + * different fingerprints. For example, if both a class and its companion object were test classes, the <code>tasks</code> method could be + * passed an array containing <code>TaskDef</code>s with the same name but with a different value for <code>fingerprint.isModule</code>. + * </p> + * + * <p> + * A test framework may "reject" a requested task by returning no <code>Task</code> for that <code>TaskDef</code>. + * </p> + * + * @param taskDefs the <code>TaskDef</code>s for requested tasks + * @return an array of <code>Task</code>s + * @throws IllegalStateException if invoked after <code>done</code> has been invoked. + */ + def tasks(taskDefs: Array[TaskDef]): Array[sbt.testing.Task] = + taskDefs map (PartestTask(_, args): sbt.testing.Task) + + /** Indicates the client is done with this <code>Runner</code> instance. + * + * @return a possibly multi-line summary string, or the empty string if no summary is provided -- TODO + */ + def done(): String = "" +} + +/** Run partest in this VM. Assumes we're running in a forked VM! + * + * TODO: make configurable + */ +case class PartestTask(taskDef: TaskDef, args: Array[String]) extends Task { + + // Get scala version through test name + val scalaVersion = taskDef.fullyQualifiedName.stripPrefix("partest-") + + /** Executes this task, possibly returning to the client new tasks to execute. */ + def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = { + val forkedCp = scala.util.Properties.javaClassPath + val classLoader = new URLClassLoader(forkedCp.split(java.io.File.pathSeparator).map(new File(_).toURI.toURL)) + + if (Runtime.getRuntime().maxMemory() / (1024*1024) < 800) + loggers foreach (_.warn(s"""Low heap size detected (~ ${Runtime.getRuntime().maxMemory() / (1024*1024)}M). Please add the following to your build.sbt: javaOptions in Test += "-Xmx1G"""")) + + val maybeOptions = + ScalaJSPartestOptions(args, str => loggers.foreach(_.error(str))) + + maybeOptions foreach { options => + val runner = SBTRunner( + Framework.fingerprint, eventHandler, loggers, + new File(s"../partest/fetchedSources/${scalaVersion}"), + classLoader, null, null, Array.empty[String], options, scalaVersion) + + try runner execute Array("run", "pos", "neg") + catch { + case ex: ClassNotFoundException => + loggers foreach { l => l.error("Please make sure partest is running in a forked VM by including the following line in build.sbt:\nfork in Test := true") } + throw ex + } + } + + Array() + } + + type SBTRunner = { def execute(kinds: Array[String]): String } + + // use reflection to instantiate scala.tools.partest.scalajs.ScalaJSSBTRunner, + // casting to the structural type SBTRunner above so that method calls on the result will be invoked reflectively as well + private def SBTRunner(partestFingerprint: Fingerprint, eventHandler: EventHandler, loggers: Array[Logger], testRoot: File, testClassLoader: URLClassLoader, javaCmd: File, javacCmd: File, scalacArgs: Array[String], options: ScalaJSPartestOptions, scalaVersion: String): SBTRunner = { + val runnerClass = Class.forName("scala.tools.partest.scalajs.ScalaJSSBTRunner") + runnerClass.getConstructors()(0).newInstance(partestFingerprint, eventHandler, loggers, testRoot, testClassLoader, javaCmd, javacCmd, scalacArgs, options, scalaVersion).asInstanceOf[SBTRunner] + } + + /** A possibly zero-length array of string tags associated with this task. */ + def tags: Array[String] = Array() +} diff --git a/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartest.scala b/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartest.scala new file mode 100644 index 0000000..edd0ea9 --- /dev/null +++ b/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartest.scala @@ -0,0 +1,203 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.tools.partest +package scalajs + +import nest._ +import Path._ + +import scala.tools.nsc.{ Global, Settings } +import scala.tools.nsc.reporters.{ Reporter } +import scala.tools.nsc.plugins.Plugin + +import scala.scalajs.compiler.ScalaJSPlugin + +import scala.io.Source + +import sbt.testing.{ EventHandler, Logger, Fingerprint } +import java.io.File +import java.net.URLClassLoader + +trait ScalaJSDirectCompiler extends DirectCompiler { + override def newGlobal(settings: Settings, reporter: Reporter): PartestGlobal = { + new PartestGlobal(settings, reporter) { + override protected def loadRoughPluginsList(): List[Plugin] = { + (super.loadRoughPluginsList() :+ + Plugin.instantiate(classOf[ScalaJSPlugin], this)) + } + } + } +} + +class ScalaJSRunner(testFile: File, suiteRunner: SuiteRunner, + scalaJSOverridePath: String, options: ScalaJSPartestOptions, + noWarnFile: File) extends nest.Runner(testFile, suiteRunner) { + + private val compliantSems: List[String] = { + scalaJSConfigFile("sem").fold(List.empty[String]) { file => + Source.fromFile(file).getLines.toList + } + } + + override val checkFile: File = { + scalaJSConfigFile("check") getOrElse { + // this is super.checkFile, but apparently we can't do that + new FileOps(testFile).changeExtension("check") + } + } + + private def scalaJSConfigFile(ext: String): Option[File] = { + val overrideFile = s"$scalaJSOverridePath/$kind/$fileBase.$ext" + val url = getClass.getResource(overrideFile) + Option(url).map(url => new File(url.toURI)) + } + + override def newCompiler = new DirectCompiler(this) with ScalaJSDirectCompiler + override def extraJavaOptions = { + super.extraJavaOptions ++ Seq( + s"-Dscalajs.partest.noWarnFile=${noWarnFile.getAbsolutePath}", + s"-Dscalajs.partest.optMode=${options.optMode.id}", + s"-Dscalajs.partest.compliantSems=${compliantSems.mkString(",")}" + ) + } +} + +trait ScalaJSSuiteRunner extends SuiteRunner { + + // Stuff to mix in + + val options: ScalaJSPartestOptions + + /** Full scala version name. Used to discover blacklist (etc.) files */ + val scalaVersion: String + + // Stuff we provide + + override def banner: String = { + import scala.scalajs.ir.ScalaJSVersions.{ current => currentVersion } + + super.banner.trim + s""" + |Scala.js version is: $currentVersion + |Scala.js options are: + |optimizer: ${options.optMode.shortStr} + |testFilter: ${options.testFilter.descr} + """.stripMargin + } + + override def runTest(testFile: File): TestState = { + // Mostly copy-pasted from SuiteRunner.runTest(), unfortunately :-( + val runner = new ScalaJSRunner(testFile, this, listDir, options, noWarnFile) + + // when option "--failed" is provided execute test only if log + // is present (which means it failed before) + val state = + if (failed && !runner.logFile.canRead) + runner.genPass() + else { + val (state, elapsed) = + try timed(runner.run()) + catch { + case t: Throwable => throw new RuntimeException(s"Error running $testFile", t) + } + NestUI.reportTest(state) + runner.cleanup() + state + } + onFinishTest(testFile, state) + } + + override def runTestsForFiles(kindFiles: Array[File], + kind: String): Array[TestState] = { + super.runTestsForFiles(kindFiles.filter(shouldUseTest), kind) + } + + private lazy val listDir = + s"/scala/tools/partest/scalajs/$scalaVersion" + + private lazy val buglistedTestFileNames = + readTestList(s"$listDir/BuglistedTests.txt") + + private lazy val blacklistedTestFileNames = + readTestList(s"$listDir/BlacklistedTests.txt") + + private lazy val whitelistedTestFileNames = + readTestList(s"$listDir/WhitelistedTests.txt") + + private lazy val noWarnFile: File = { + val url = getClass.getResource(s"$listDir/NoDCEWarn.txt") + assert(url != null, "Need NoDCEWarn.txt file") + new File(url.toURI).getAbsolutePath() + } + + private def readTestList(resourceName: String): Set[String] = { + val source = scala.io.Source.fromURL(getClass.getResource(resourceName)) + + val fileNames = for { + line <- source.getLines + trimmed = line.trim + if trimmed != "" && !trimmed.startsWith("#") + } yield extendShortTestName(trimmed) + + fileNames.toSet + } + + private def extendShortTestName(testName: String) = { + val srcDir = PathSettings.srcDir + (srcDir / testName).toCanonical.getAbsolutePath + } + + private lazy val testFilter: String => Boolean = { + import ScalaJSPartestOptions._ + options.testFilter match { + case UnknownTests => { absPath => + !blacklistedTestFileNames.contains(absPath) && + !whitelistedTestFileNames.contains(absPath) && + !buglistedTestFileNames.contains(absPath) + } + case BlacklistedTests => blacklistedTestFileNames + case BuglistedTests => buglistedTestFileNames + case WhitelistedTests => whitelistedTestFileNames + case SomeTests(names) => names.map(extendShortTestName _).toSet + } + } + + private def shouldUseTest(testFile: File): Boolean = { + val absPath = testFile.toCanonical.getAbsolutePath + testFilter(absPath) + } +} + +/* Pre-mixin ScalaJSSuiteRunner in SBTRunner, because this is looked up + * via reflection from the sbt partest interface of Scala.js + */ +class ScalaJSSBTRunner( + partestFingerprint: Fingerprint, + eventHandler: EventHandler, + loggers: Array[Logger], + testRoot: File, + testClassLoader: URLClassLoader, + javaCmd: File, + javacCmd: File, + scalacArgs: Array[String], + val options: ScalaJSPartestOptions, + val scalaVersion: String +) extends SBTRunner( + partestFingerprint, eventHandler, loggers, "test/files", testClassLoader, + javaCmd, javacCmd, scalacArgs +) with ScalaJSSuiteRunner { + + // The test root for partest is read out through the system properties, + // not passed as an argument + sys.props("partest.root") = testRoot.getAbsolutePath() + + // Partests take at least 5h. We double, just to be sure. (default is 4 hours) + sys.props("partest.timeout") = "10 hours" + + // Set showDiff on global UI module + if (options.showDiff) + NestUI.setDiffOnFail() + +} diff --git a/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartestOptions.scala b/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartestOptions.scala new file mode 100644 index 0000000..1f1680a --- /dev/null +++ b/examples/scala-js/partest/src/main/scala/scala/tools/partest/scalajs/ScalaJSPartestOptions.scala @@ -0,0 +1,109 @@ +package scala.tools.partest.scalajs + +class ScalaJSPartestOptions private ( + val testFilter: ScalaJSPartestOptions.TestFilter, + val optMode: ScalaJSPartestOptions.OptMode, + val showDiff: Boolean +) + +object ScalaJSPartestOptions { + + sealed abstract class TestFilter { + def descr: String + } + case object UnknownTests extends TestFilter { + override def descr: String = "Unknown" + } + case object BlacklistedTests extends TestFilter { + override def descr: String = "Blacklisted" + } + case object WhitelistedTests extends TestFilter { + override def descr: String = "Whitelisted" + } + case object BuglistedTests extends TestFilter { + override def descr: String = "Buglisted" + } + case class SomeTests(names: List[String]) extends TestFilter { + override def descr: String = "Custom " + this.toString + override def toString() = + names.map(x => s""""$x"""").mkString("[", ", ", "]") + } + + sealed abstract class OptMode { + def shortStr: String + def id: String + } + object OptMode { + def fromId(id: String): OptMode = id match { + case "none" => NoOpt + case "fast" => FastOpt + case "full" => FullOpt + case _ => sys.error(s"Unknown optimization mode: $id") + } + } + case object NoOpt extends OptMode { + def shortStr: String = "None" + def id: String = "none" + } + case object FastOpt extends OptMode { + def shortStr: String = "Fast" + def id: String = "fast" + } + case object FullOpt extends OptMode { + def shortStr: String = "Full" + def id: String = "full" + } + + def apply(args: Array[String], + errorReporter: String => Unit): Option[ScalaJSPartestOptions] = { + + var failed = false + + var filter: Option[TestFilter] = None + var optMode: OptMode = NoOpt + var showDiff: Boolean = false + + def error(msg: String) = { + failed = true + errorReporter(msg) + } + + def setFilter(newFilter: TestFilter) = (filter, newFilter) match { + case (Some(SomeTests(oldNames)), SomeTests(newNames)) => + // Merge test names + filter = Some(SomeTests(oldNames ++ newNames)) + case (Some(fil), newFilter) => + error(s"You cannot specify twice what tests to use (already specified: $fil, new: $newFilter)") + case (None, newFilter) => + filter = Some(newFilter) + } + + for (arg <- args) arg match { + case "--fastOpt" => + optMode = FastOpt + case "--noOpt" => + optMode = NoOpt + case "--fullOpt" => + optMode = FullOpt + case "--blacklisted" => + setFilter(BlacklistedTests) + case "--buglisted" => + setFilter(BuglistedTests) + case "--whitelisted" => + setFilter(WhitelistedTests) + case "--unknown" => + setFilter(UnknownTests) + case "--showDiff" => + showDiff = true + case _ => + setFilter(SomeTests(arg :: Nil)) + } + + if (failed) None + else Some { + new ScalaJSPartestOptions( + filter.getOrElse(WhitelistedTests), optMode, showDiff) + } + } + +} |