From 5be2c5aea4527cf637948e6bf2e4c56e3273fbd9 Mon Sep 17 00:00:00 2001 From: Nikolay Tatarinov <5min4eq.unity@gmail.com> Date: Thu, 1 Feb 2018 22:07:39 +0300 Subject: WIP: Scala js testing (#119) fixes #102. Use scala js testing framework to launch tests --- scalajslib/src/mill/scalajslib/ScalaJSBridge.scala | 72 ++++++++++++ .../src/mill/scalajslib/ScalaJSLinkerBridge.scala | 72 ------------ scalajslib/src/mill/scalajslib/ScalaJSModule.scala | 127 +++++++++++++++++---- 3 files changed, 175 insertions(+), 96 deletions(-) create mode 100644 scalajslib/src/mill/scalajslib/ScalaJSBridge.scala delete mode 100644 scalajslib/src/mill/scalajslib/ScalaJSLinkerBridge.scala (limited to 'scalajslib/src') diff --git a/scalajslib/src/mill/scalajslib/ScalaJSBridge.scala b/scalajslib/src/mill/scalajslib/ScalaJSBridge.scala new file mode 100644 index 00000000..10dd42d6 --- /dev/null +++ b/scalajslib/src/mill/scalajslib/ScalaJSBridge.scala @@ -0,0 +1,72 @@ +package mill.scalajslib + +import java.io.File +import java.net.URLClassLoader + +import ammonite.ops.Path +import mill.{Agg, T} + +sealed trait OptimizeMode + +object FastOpt extends OptimizeMode +object FullOpt extends OptimizeMode + +class ScalaJSWorker { + private var scalaInstanceCache = Option.empty[(Long, ScalaJSBridge)] + + private def bridge(toolsClasspath: Agg[Path]) = { + val classloaderSig = + toolsClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum + scalaInstanceCache match { + case Some((sig, bridge)) if sig == classloaderSig => bridge + case _ => + val cl = new URLClassLoader(toolsClasspath.map(_.toIO.toURI.toURL).toArray) + val bridge = cl + .loadClass("mill.scalajslib.bridge.ScalaJSBridge") + .getDeclaredConstructor() + .newInstance() + .asInstanceOf[ScalaJSBridge] + scalaInstanceCache = Some((classloaderSig, bridge)) + bridge + } + } + + def link(toolsClasspath: Agg[Path], + sources: Agg[Path], + libraries: Agg[Path], + dest: File, + main: Option[String], + fullOpt: Boolean): Unit = { + bridge(toolsClasspath).link( + sources.items.map(_.toIO).toArray, + libraries.items.map(_.toIO).toArray, + dest, + main.orNull, + fullOpt + ) + } + + def getFramework(toolsClasspath: Agg[Path], + frameworkName: String, + linkedFile: File): sbt.testing.Framework = { + bridge(toolsClasspath).getFramework(frameworkName, linkedFile) + } + +} + +trait ScalaJSBridge { + def link(sources: Array[File], + libraries: Array[File], + dest: File, + main: String, + fullOpt: Boolean): Unit + + def getFramework(frameworkName: String, + linkedFile: File): sbt.testing.Framework + +} + +object ScalaJSBridge extends mill.define.BaseModule(ammonite.ops.pwd) { + + def scalaJSBridge = T.worker { new ScalaJSWorker() } +} diff --git a/scalajslib/src/mill/scalajslib/ScalaJSLinkerBridge.scala b/scalajslib/src/mill/scalajslib/ScalaJSLinkerBridge.scala deleted file mode 100644 index ae282152..00000000 --- a/scalajslib/src/mill/scalajslib/ScalaJSLinkerBridge.scala +++ /dev/null @@ -1,72 +0,0 @@ -package mill.scalajslib - -import java.io.File -import java.net.URLClassLoader - -import ammonite.ops.Path -import mill.{Agg, T} - -sealed trait OptimizeMode - -object FastOpt extends OptimizeMode -object FullOpt extends OptimizeMode - -class ScalaJSLinkerWorker { - var scalaInstanceCache = Option.empty[(Long, ScalaJSLinkerBridge)] - def link(toolsClasspath: Agg[Path], - sources: Agg[Path], - libraries: Agg[Path], - dest: File, - main: Option[String], - fullOpt: Boolean): Unit = { - - val classloaderSig = { - toolsClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum - } - - val bridge = scalaInstanceCache match{ - case Some((sig, bridge)) if sig == classloaderSig => bridge - case _ => - val outerClassLoader = getClass.getClassLoader - val cl = new URLClassLoader(toolsClasspath.map(_.toIO.toURI.toURL).toArray){ - override def findClass(name: String) = { - if (name.startsWith("mill.scalajslib.ScalaJSLinkerBridge")){ - outerClassLoader.loadClass(name) - }else{ - super.findClass(name) - } - } - } - val bridge = cl - .loadClass("mill.scalajslib.bridge.ScalaJSLinkerBridge") - .getDeclaredConstructor() - .newInstance() - .asInstanceOf[ScalaJSLinkerBridge] - scalaInstanceCache = Some((classloaderSig, bridge)) - bridge - } - bridge.link( - sources.items.map(_.toIO).toArray, - libraries.items.map(_.toIO).toArray, - dest, - main.orNull, - fullOpt - ) - } - -} - -trait ScalaJSLinkerBridge { - def link(sources: Array[File], - libraries: Array[File], - dest: File, - main: String, - fullOpt: Boolean): Unit -} - -object ScalaJSLinkerBridge extends mill.define.BaseModule(ammonite.ops.pwd){ - - def scalaJSLinkerBridge = T.worker{ - new ScalaJSLinkerWorker() - } -} \ No newline at end of file diff --git a/scalajslib/src/mill/scalajslib/ScalaJSModule.scala b/scalajslib/src/mill/scalajslib/ScalaJSModule.scala index d76a50db..82000c6e 100644 --- a/scalajslib/src/mill/scalajslib/ScalaJSModule.scala +++ b/scalajslib/src/mill/scalajslib/ScalaJSModule.scala @@ -1,24 +1,25 @@ package mill package scalajslib -import java.io.File - -import mill.scalalib.DepSyntax import ammonite.ops.{Path, ls, mkdir, rm} import coursier.Cache import coursier.maven.MavenRepository import mill.eval.PathRef import mill.eval.Result.Success import mill.scalalib.Lib.resolveDependencies -import mill.scalalib.{Dep, PublishModule, ScalaModule, TestModule} -import mill.util.Loose - -import scala.collection.breakOut +import mill.scalalib.{CompilationResult, Dep, DepSyntax, TestModule} +import mill.util.{Ctx, Loose} trait ScalaJSModule extends scalalib.ScalaModule { outer => def scalaJSVersion: T[String] + trait Tests extends TestScalaJSModule { + override def scalaVersion = outer.scalaVersion() + override def scalaJSVersion = outer.scalaJSVersion() + override def moduleDeps = Seq(outer) + } + private val ReleaseVersion = raw"""(\d+)\.(\d+)\.(\d+)""".r private val MinorSnapshotVersion = raw"""(\d+)\.(\d+)\.([1-9]\d*)-SNAPSHOT""".r @@ -32,7 +33,7 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer => def scalaJSBridgeVersion = T{ scalaJSVersion().split('.').dropRight(1).mkString(".") } - def sjsLinkerBridgeClasspath = T { + def sjsBridgeClasspath = T { val jsBridgeKey = "MILL_SCALAJS_BRIDGE_" + scalaJSBridgeVersion().replace('.', '_') val jsBridgePath = sys.props(jsBridgeKey) if (jsBridgePath != null) Success( @@ -49,41 +50,78 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer => ).map(_.find(_.path.toString.contains("mill-jsbridge")).get) } - def scalaJSLinkerClasspath: T[Loose.Agg[PathRef]] = T{ + val commonDeps = Seq( + ivy"org.scala-js::scalajs-tools:${scalaJSVersion()}", + ivy"org.scala-js::scalajs-sbt-test-adapter:${scalaJSVersion()}" + ) + val envDep = scalaJSBinaryVersion() match { + case v if v.startsWith("0.6") => ivy"org.scala-js::scalajs-js-envs:${scalaJSVersion()}" + case v if v.startsWith("1.0") => ivy"org.scala-js::scalajs-env-nodejs:${scalaJSVersion()}" + } resolveDependencies( repositories, "2.12.4", "2.12", - Seq(ivy"org.scala-js::scalajs-tools:${scalaJSVersion()}") + commonDeps :+ envDep + ) + } + + def toolsClasspath = T { Agg(sjsBridgeClasspath()) ++ scalaJSLinkerClasspath() } + + def fastOpt = T { + link( + ScalaJSBridge.scalaJSBridge(), + toolsClasspath(), + Seq(compile()), + compileDepClasspath(), + mainClass(), + FastOpt + ) + } + + def fullOpt = T { + link( + ScalaJSBridge.scalaJSBridge(), + toolsClasspath(), + Seq(compile()), + compileDepClasspath(), + mainClass(), + FullOpt ) } - def genericOpt(mode: OptimizeMode) = T.task{ - val outputPath = T.ctx().dest / "out.js" + def link(worker: ScalaJSWorker, + toolsClasspath: Agg[PathRef], + input: Seq[CompilationResult], + libraries: Agg[PathRef], + mainClass: Option[String], + mode: OptimizeMode)(implicit ctx: Ctx.DestCtx): PathRef = { + val outputPath = ctx.dest / "out.js" - mkdir(T.ctx().dest) + mkdir(ctx.dest) rm(outputPath) - val inputFiles = Agg.from(ls(compile().classes.path).filter(_.ext == "sjsir")) - val inputLibraries = compileDepClasspath().map(_.path).filter(_.ext == "jar") - mill.scalajslib.ScalaJSLinkerBridge.scalaJSLinkerBridge().link( - (Agg(sjsLinkerBridgeClasspath()) ++ scalaJSLinkerClasspath()).map(_.path), + + val inputFiles = Agg.from(for { + compiled <- input + file <- ls(compiled.classes.path) + if file.ext == "sjsir" + } yield file) + val inputLibraries = libraries.map(_.path).filter(_.ext == "jar") + worker.link( + toolsClasspath.map(_.path), inputFiles, inputLibraries, outputPath.toIO, - mainClass(), + mainClass, mode == FullOpt ) PathRef(outputPath) } - def fastOpt = T{ genericOpt(FastOpt)() } - - def fullOpt = T{ genericOpt(FullOpt)() } - override def scalacPluginIvyDeps = T{ Loose.Agg(Dep.Point("org.scala-js", "scalajs-compiler", scalaJSVersion())) } - override def ivyDeps = T{ Loose.Agg(Dep("org.scala-js", "scalajs-library", scalaJSVersion())) } + override def ivyDeps = T{ Loose.Agg(ivy"org.scala-js::scalajs-library:${scalaJSVersion()}") } // publish artifact with name "mill_sjs0.6.4_2.12" instead of "mill_sjs0.6_2.12" def crossFullScalaJSVersion: T[Boolean] = false @@ -96,4 +134,45 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer => } -trait TestScalaJSModule extends ScalaJSModule with TestModule \ No newline at end of file +trait TestScalaJSModule extends ScalaJSModule with TestModule { + def scalaJSTestDeps = T { + resolveDeps(T.task { + Loose.Agg( + ivy"org.scala-js::scalajs-library:${scalaJSVersion()}", + ivy"org.scala-js::scalajs-test-interface:${scalaJSVersion()}" + ) + }) + } + + def fastOptTest = T { + link( + ScalaJSBridge.scalaJSBridge(), + toolsClasspath(), + compile() +: upstreamCompileOutput(), + scalaJSTestDeps() ++ compileDepClasspath(), + None, + FastOpt + ) + } + + override def testLocal(args: String*) = T.command { test(args:_*) } + + override def test(args: String*) = T.command { + val framework = mill.scalajslib.ScalaJSBridge.scalaJSBridge().getFramework( + toolsClasspath().map(_.path), + testFramework(), + fastOptTest().path.toIO + ) + + val (doneMsg, results) = mill.scalalib.ScalaWorkerApi + .scalaWorker() + .apply( + _ => framework, + runClasspath().map(_.path), + Agg(compile().classes.path), + args + ) + TestModule.handleResults(doneMsg, results) + } + +} -- cgit v1.2.3