From 826e8e796942f5f978596e07f68e79dc92033f7f Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Wed, 9 Apr 2014 14:38:41 +0200 Subject: [PATCH 01/84] Add full support for projects requiring DOM - Fix scala-js/scala-js#442: Add Phantom.js environment - Add a setting whether a project requires the DOM --- .../env/phantomjs/PhantomJSEnv.scala | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala new file mode 100644 index 000000000..136cf2b8b --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -0,0 +1,157 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.env.phantomjs + +import scala.scalajs.sbtplugin.env._ + +import scala.scalajs.tools.io._ +import scala.scalajs.tools.classpath._ +import scala.scalajs.tools.env._ +import scala.scalajs.tools.logging._ + +import scala.scalajs.sbtplugin.JSUtils._ + +import java.io.{ Console => _, _ } +import scala.io.Source + +class PhantomJSEnv( + phantomjsPath: Option[String], + addEnv: Seq[String]) extends ExternalJSEnv(Seq.empty, addEnv) { + + import ExternalJSEnv._ + + protected def vmName: String = "PhantomJS" + protected def executable: String = phantomjsPath.getOrElse("phantomjs") + + // Helper constructors + def this( + phantomjsPath: String, + env: Seq[String] = Seq.empty) = this(Some(phantomjsPath), env) + + def this() = this(None, Seq.empty) + + override protected def getVMArgs(args: RunJSArgs) = + // Add launcher file to arguments + Seq(createTmpLauncherFile(args).getAbsolutePath) + + /** In phantom.js, we include JS using HTML */ + override protected def writeJSFile(file: VirtualJSFile, writer: Writer) = { + file match { + case file: FileVirtualJSFile => + val fname = toJSstr(file.file.getAbsolutePath) + writer.write( + s"""""" + "\n") + case _ => + writer.write("""\n") + } + } + + /** + * PhantomJS doesn't support Function.prototype.bind. We polyfill it. + * https://github.com/ariya/phantomjs/issues/10522 + */ + override protected def initFiles(args: RunJSArgs): Seq[VirtualJSFile] = Seq( + new MemVirtualJSFile("bindPolyfill.js").withContent( + """ + |// Polyfill for Function.bind in Mozilla MDN by Mozilla Contributors + |// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind + |// Licensed under CC-BY-SA 2.5 + |if (!Function.prototype.bind) { + | Function.prototype.bind = function (oThis) { + | if (typeof this !== "function") { + | // closest thing possible to the ECMAScript 5 internal IsCallable function + | throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); + | } + | + | var aArgs = Array.prototype.slice.call(arguments, 1), + | fToBind = this, + | fNOP = function () {}, + | fBound = function () { + | return fToBind.apply(this instanceof fNOP && oThis + | ? this + | : oThis, + | aArgs.concat(Array.prototype.slice.call(arguments))); + | }; + | + | fNOP.prototype = this.prototype; + | fBound.prototype = new fNOP(); + | + | return fBound; + | }; + |} + |""".stripMargin + ) + ) + + protected def writeWebpageLauncher(args: RunJSArgs, out: Writer): Unit = { + out.write("\n\nPhantom.js Launcher\n") + sendJS(getLibJSFiles(args), out) + writeCodeLauncher(args.code, out) + out.write("\n\n\n") + } + + private def writeCodeLauncher(code: VirtualJSFile, out: Writer): Unit = { + out.write("""\n") + } + + private def createTmpLauncherFile(args: RunJSArgs): File = { + val webF = createTmpWebpage(args) + + val launcherTmpF = File.createTempFile("phantomjs-launcher", ".js") + launcherTmpF.deleteOnExit() + + val out = new FileWriter(launcherTmpF) + + try { + out.write( + s"""// Scala.js Phantom.js launcher + |var page = require('webpage').create(); + |var url = ${toJSstr(webF.getAbsolutePath)}; + |page.onConsoleMessage = function(msg) { + | console.log(msg); + |}; + |page.open(url, function (status) { + | phantom.exit(status != 'success'); + |}); + |""".stripMargin) + } finally { + out.close() + } + + launcherTmpF + } + + private def createTmpWebpage(args: RunJSArgs): File = { + val webTmpF = File.createTempFile("phantomjs-launcher-webpage", ".html") + webTmpF.deleteOnExit() + + val out = new BufferedWriter(new FileWriter(webTmpF)) + try { + writeWebpageLauncher(args, out) + } finally { + out.close() + } + + args.logger.debug( + "PhantomJS using webpage launcher at: " + webTmpF.getAbsolutePath()) + + webTmpF + } + +} From 52bdb27c1c8dceec3c06bff3012533f9a76849d0 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Wed, 7 May 2014 10:41:02 +0200 Subject: [PATCH 02/84] Fix scala-js/scala-js#595: Test SBT plugin in published form on Travis --- sbt-plugin-test/build.sbt | 22 ++++++++++++++++++++++ sbt-plugin-test/project/build.sbt | 2 ++ 2 files changed, 24 insertions(+) create mode 100644 sbt-plugin-test/build.sbt create mode 100644 sbt-plugin-test/project/build.sbt diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt new file mode 100644 index 000000000..3327c4449 --- /dev/null +++ b/sbt-plugin-test/build.sbt @@ -0,0 +1,22 @@ +name := "Scala.js SBT test" + +version := scalaJSVersion + +val baseSettings = scalaJSSettings ++ Seq( + version := scalaJSVersion, + scalaVersion := "2.11.0", + libraryDependencies += + "org.scala-lang.modules.scalajs" %% "scalajs-jasmine-test-framework" % scalaJSVersion % "test" +) + +lazy val root = project.in(file(".")).aggregate(noDOM, withDOM) + +lazy val noDOM = project.settings(baseSettings: _*).settings( + name := "Scala.js SBT test w/o DOM" +) + +lazy val withDOM = project.settings(baseSettings: _*).settings( + ScalaJSKeys.requiresDOM := true, + ScalaJSKeys.jsDependencies += + "org.webjars" % "jquery" % "1.10.2" / "jquery.js" +) diff --git a/sbt-plugin-test/project/build.sbt b/sbt-plugin-test/project/build.sbt new file mode 100644 index 000000000..da8f2d32d --- /dev/null +++ b/sbt-plugin-test/project/build.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % + scala.scalajs.ir.ScalaJSVersions.current) From 34f7971e13a168ca7bf82f36c9c654eb35ffdbb6 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Thu, 3 Jul 2014 17:52:27 +0200 Subject: [PATCH 03/84] Fix scala-js/scala-js#799: Make PhantomJS runner fail if JS code fails --- .../env/phantomjs/PhantomJSEnv.scala | 11 ++++++++++ .../sbtplugin/test/env/PhantomJSTest.scala | 21 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala index 136cf2b8b..d16d13315 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -126,6 +126,17 @@ class PhantomJSEnv( |page.onConsoleMessage = function(msg) { | console.log(msg); |}; + |page.onError = function(msg, trace) { + | console.error(msg); + | if (trace && trace.length) { + | console.error(''); + | trace.forEach(function(t) { + | console.error(' ' + t.file + ':' + t.line + (t.function ? ' (in function "' + t.function +'")' : '')); + | }); + | } + | + | phantom.exit(2); + |}; |page.open(url, function (status) { | phantom.exit(status != 'success'); |}); diff --git a/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala b/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala new file mode 100644 index 000000000..bdb33e57d --- /dev/null +++ b/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala @@ -0,0 +1,21 @@ +package scala.scalajs.sbtplugin.test.env + +import scala.scalajs.sbtplugin.env.phantomjs.PhantomJSEnv + +import org.junit.Test + +class PhantomJSTest extends JSEnvTest { + + protected def newJSEnv = new PhantomJSEnv + + @Test + def failureTest = { + + """ + var a = {}; + a.foo(); + """.fails() + + } + +} From 78d3a064b0dd3e8dd7e8c1a76872850a02ae7cc2 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 11 Jul 2014 11:59:55 +0200 Subject: [PATCH 04/84] Fix scala-js/scala-js#820: Properly escape script file names in PhantomJS runner --- .../sbtplugin/env/phantomjs/PhantomJSEnv.scala | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala index d16d13315..2d98e7548 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -45,9 +45,9 @@ class PhantomJSEnv( override protected def writeJSFile(file: VirtualJSFile, writer: Writer) = { file match { case file: FileVirtualJSFile => - val fname = toJSstr(file.file.getAbsolutePath) + val fname = htmlEscape(file.file.getAbsolutePath) writer.write( - s"""""" + "\n") + s"""""" + "\n") case _ => writer.write("""""" + "\n") - case _ => - writer.write("""\n") - } + override def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): JSRunner = { + new PhantomRunner(classpath, code, logger, console) } - def htmlEscape(str: String): String = str.flatMap { - case '<' => "<" - case '>' => ">" - case '"' => """ - case '&' => "&" - case c => c :: Nil + override def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): AsyncJSRunner = { + new AsyncPhantomRunner(classpath, code, logger, console) } - /** - * PhantomJS doesn't support Function.prototype.bind. We polyfill it. - * https://github.com/ariya/phantomjs/issues/10522 - */ - override protected def initFiles(args: RunJSArgs): Seq[VirtualJSFile] = Seq( - new MemVirtualJSFile("bindPolyfill.js").withContent( - """ - |// Polyfill for Function.bind from Facebook react: - |// https://github.com/facebook/react/blob/3dc10749080a460e48bee46d769763ec7191ac76/src/test/phantomjs-shims.js - |// Originally licensed under Apache 2.0 - |(function() { - | - | var Ap = Array.prototype; - | var slice = Ap.slice; - | var Fp = Function.prototype; - | - | if (!Fp.bind) { - | // PhantomJS doesn't support Function.prototype.bind natively, so - | // polyfill it whenever this module is required. - | Fp.bind = function(context) { - | var func = this; - | var args = slice.call(arguments, 1); - | - | function bound() { - | var invokedAsConstructor = func.prototype && (this instanceof func); - | return func.apply( - | // Ignore the context parameter when invoking the bound function - | // as a constructor. Note that this includes not only constructor - | // invocations using the new keyword but also calls to base class - | // constructors such as BaseClass.call(this, ...) or super(...). - | !invokedAsConstructor && context || this, - | args.concat(slice.call(arguments)) - | ); - | } - | - | // The bound function must share the .prototype of the unbound - | // function so that any object created by one constructor will count - | // as an instance of both constructors. - | bound.prototype = func.prototype; - | - | return bound; - | }; - | } - | - |})(); - |""".stripMargin - ), - new MemVirtualJSFile("scalaJSEnvInfo.js").withContent( - """ - |__ScalaJSEnv = { - | exitFunction: function(status) { window.callPhantom(status); } - |}; - """.stripMargin - ) - ) - - protected def writeWebpageLauncher(args: RunJSArgs, out: Writer): Unit = { - out.write("\n\nPhantom.js Launcher\n") - sendJS(getLibJSFiles(args), out) - writeCodeLauncher(args.code, out) - out.write("\n\n\n") - } + protected class PhantomRunner(classpath: CompleteClasspath, + code: VirtualJSFile, logger: Logger, console: JSConsole + ) extends ExtRunner(classpath, code, logger, console) + with AbstractPhantomRunner + + protected class AsyncPhantomRunner(classpath: CompleteClasspath, + code: VirtualJSFile, logger: Logger, console: JSConsole + ) extends AsyncExtRunner(classpath, code, logger, console) + with AbstractPhantomRunner + + protected trait AbstractPhantomRunner extends AbstractExtRunner { + + override protected def getVMArgs() = + // Add launcher file to arguments + additionalArgs :+ createTmpLauncherFile().getAbsolutePath + + /** In phantom.js, we include JS using HTML */ + override protected def writeJSFile(file: VirtualJSFile, writer: Writer) = { + file match { + case file: FileVirtualJSFile => + val fname = htmlEscape(file.file.getAbsolutePath) + writer.write( + s"""""" + "\n") + case _ => + writer.write("""\n") + } + } - private def writeCodeLauncher(code: VirtualJSFile, out: Writer): Unit = { - out.write("""\n") - } + /** + * PhantomJS doesn't support Function.prototype.bind. We polyfill it. + * https://github.com/ariya/phantomjs/issues/10522 + */ + override protected def initFiles(): Seq[VirtualJSFile] = Seq( + new MemVirtualJSFile("bindPolyfill.js").withContent( + """ + |// Polyfill for Function.bind from Facebook react: + |// https://github.com/facebook/react/blob/3dc10749080a460e48bee46d769763ec7191ac76/src/test/phantomjs-shims.js + |// Originally licensed under Apache 2.0 + |(function() { + | + | var Ap = Array.prototype; + | var slice = Ap.slice; + | var Fp = Function.prototype; + | + | if (!Fp.bind) { + | // PhantomJS doesn't support Function.prototype.bind natively, so + | // polyfill it whenever this module is required. + | Fp.bind = function(context) { + | var func = this; + | var args = slice.call(arguments, 1); + | + | function bound() { + | var invokedAsConstructor = func.prototype && (this instanceof func); + | return func.apply( + | // Ignore the context parameter when invoking the bound function + | // as a constructor. Note that this includes not only constructor + | // invocations using the new keyword but also calls to base class + | // constructors such as BaseClass.call(this, ...) or super(...). + | !invokedAsConstructor && context || this, + | args.concat(slice.call(arguments)) + | ); + | } + | + | // The bound function must share the .prototype of the unbound + | // function so that any object created by one constructor will count + | // as an instance of both constructors. + | bound.prototype = func.prototype; + | + | return bound; + | }; + | } + | + |})(); + |""".stripMargin + ), + new MemVirtualJSFile("scalaJSEnvInfo.js").withContent( + """ + |__ScalaJSEnv = { + | exitFunction: function(status) { window.callPhantom(status); } + |}; + """.stripMargin + ) + ) + + protected def writeWebpageLauncher(out: Writer): Unit = { + out.write("\n\nPhantom.js Launcher\n") + sendJS(getLibJSFiles(), out) + writeCodeLauncher(code, out) + out.write("\n\n\n") + } - private def createTmpLauncherFile(args: RunJSArgs): File = { - val webF = createTmpWebpage(args) - - val launcherTmpF = File.createTempFile("phantomjs-launcher", ".js") - launcherTmpF.deleteOnExit() - - val out = new FileWriter(launcherTmpF) - - try { - out.write( - s"""// Scala.js Phantom.js launcher - |var page = require('webpage').create(); - |var url = ${toJSstr(webF.getAbsolutePath)}; - |page.onConsoleMessage = function(msg) { - | console.log(msg); - |}; - |page.onError = function(msg, trace) { - | console.error(msg); - | if (trace && trace.length) { - | console.error(''); - | trace.forEach(function(t) { - | console.error(' ' + t.file + ':' + t.line + (t.function ? ' (in function "' + t.function +'")' : '')); - | }); - | } - | - | phantom.exit(2); - |}; - |page.onCallback = function(status) { - | phantom.exit(status); - |}; - |""".stripMargin) - if (autoExit) { - out.write(""" - page.open(url, function (status) { - phantom.exit(status != 'success'); - });""") - } else { - out.write(""" - page.open(url, function (status) { - if (status != 'success') phantom.exit(1); - });""") + protected def createTmpLauncherFile(): File = { + val webF = createTmpWebpage() + + val launcherTmpF = File.createTempFile("phantomjs-launcher", ".js") + launcherTmpF.deleteOnExit() + + val out = new FileWriter(launcherTmpF) + + try { + out.write( + s"""// Scala.js Phantom.js launcher + |var page = require('webpage').create(); + |var url = ${toJSstr(webF.getAbsolutePath)}; + |page.onConsoleMessage = function(msg) { + | console.log(msg); + |}; + |page.onError = function(msg, trace) { + | console.error(msg); + | if (trace && trace.length) { + | console.error(''); + | trace.forEach(function(t) { + | console.error(' ' + t.file + ':' + t.line + (t.function ? ' (in function "' + t.function +'")' : '')); + | }); + | } + | + | phantom.exit(2); + |}; + |page.onCallback = function(status) { + | phantom.exit(status); + |}; + |""".stripMargin) + if (autoExit) { + out.write(""" + page.open(url, function (status) { + phantom.exit(status != 'success'); + });""") + } else { + out.write(""" + page.open(url, function (status) { + if (status != 'success') phantom.exit(1); + });""") + } + } finally { + out.close() } - } finally { - out.close() + + launcherTmpF } - launcherTmpF - } + protected def createTmpWebpage(): File = { + val webTmpF = File.createTempFile("phantomjs-launcher-webpage", ".html") + webTmpF.deleteOnExit() + + val out = new BufferedWriter(new FileWriter(webTmpF)) + try { + writeWebpageLauncher(out) + } finally { + out.close() + } - private def createTmpWebpage(args: RunJSArgs): File = { - val webTmpF = File.createTempFile("phantomjs-launcher-webpage", ".html") - webTmpF.deleteOnExit() + logger.debug( + "PhantomJS using webpage launcher at: " + webTmpF.getAbsolutePath()) - val out = new BufferedWriter(new FileWriter(webTmpF)) - try { - writeWebpageLauncher(args, out) - } finally { - out.close() + webTmpF } - args.logger.debug( - "PhantomJS using webpage launcher at: " + webTmpF.getAbsolutePath()) + protected def writeCodeLauncher(code: VirtualJSFile, out: Writer): Unit = { + out.write("""\n") + } + } - webTmpF + protected def htmlEscape(str: String): String = str.flatMap { + case '<' => "<" + case '>' => ">" + case '"' => """ + case '&' => "&" + case c => c :: Nil } } From 8c35f58f52d3b0f4f3d1ab58fc9817ec80a1800a Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Mon, 3 Nov 2014 14:33:25 +0100 Subject: [PATCH 12/84] Remove JSEnv constructor-blow up. Use default args --- .../env/phantomjs/PhantomJSEnv.scala | 66 ++----------------- 1 file changed, 6 insertions(+), 60 deletions(-) diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala index 4ff05abac..0fb649a81 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -22,68 +22,14 @@ import java.io.{ Console => _, _ } import scala.io.Source class PhantomJSEnv( - phantomjsPath: Option[String], - addArgs: Seq[String], - addEnv: Map[String, String], - val autoExit: Boolean) extends ExternalJSEnv(addArgs, addEnv) { + phantomjsPath: String = "phantomjs", + addArgs: Seq[String] = Seq.empty, + addEnv: Map[String, String] = Map.empty, + val autoExit: Boolean = true +) extends ExternalJSEnv(addArgs, addEnv) { protected def vmName: String = "PhantomJS" - protected def executable: String = phantomjsPath.getOrElse("phantomjs") - - // Helper constructors - - def this(phantomjsPath: Option[String], addArgs: Seq[String], autoExit: Boolean) = - this(phantomjsPath, addArgs, Map.empty[String, String], autoExit) - - def this(phantomjsPath: Option[String], addEnv: Map[String, String], autoExit: Boolean) = - this(phantomjsPath, Seq.empty, addEnv, autoExit) - - def this(phantomjsPath: Option[String], addArgs: Seq[String]) = - this(phantomjsPath, addArgs, Map.empty[String, String], true) - - def this(phantomjsPath: Option[String], addEnv: Map[String, String]) = - this(phantomjsPath, Seq.empty, addEnv, true) - - def this(phantomjsPath: String, args: Seq[String], env: Map[String, String], autoExit: Boolean) = - this(Some(phantomjsPath), args, env, autoExit) - - def this(phantomjsPath: String, args: Seq[String], autoExit: Boolean) = - this(Some(phantomjsPath), args, Map.empty[String, String], autoExit) - - def this(phantomjsPath: String, env: Map[String, String], autoExit: Boolean) = - this(Some(phantomjsPath), Seq.empty, env, autoExit) - - def this(phantomjsPath: String, args: Seq[String], env: Map[String, String]) = - this(Some(phantomjsPath), args, env, true) - - def this(phantomjsPath: String, args: Seq[String]) = - this(Some(phantomjsPath), args, Map.empty[String, String], true) - - def this(phantomjsPath: String, env: Map[String, String]) = - this(Some(phantomjsPath), Seq.empty, env, true) - - def this(args: Seq[String], env: Map[String, String], autoExit: Boolean) = - this(None, args, env, autoExit) - - def this(args: Seq[String], env: Map[String, String]) = - this(None, args, env, true) - - def this(args: Seq[String], autoExit: Boolean) = - this(None, args, Map.empty[String, String], autoExit) - - def this(env: Map[String, String], autoExit: Boolean) = - this(None, Seq.empty, env, autoExit) - - def this(args: Seq[String]) = - this(None, args, Map.empty[String, String], true) - - def this(env: Map[String, String]) = - this(None, Seq.empty, env, true) - - def this(autoExit: Boolean) = - this(None, Seq.empty, Map.empty[String, String], autoExit) - - def this() = this(None, Seq.empty, Map.empty[String, String], true) + protected def executable: String = phantomjsPath override def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, logger: Logger, console: JSConsole): JSRunner = { From ee094891128ac5c0e758d96c942391a7a70d658a Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 4 Nov 2014 14:59:00 +0100 Subject: [PATCH 13/84] Clean up standalone sbt test project --- sbt-plugin-test/build.sbt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index dc1c1e94e..c1dc0fbef 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -1,4 +1,8 @@ -name := "Scala.js SBT test" +import ScalaJSKeys._ + +import scala.scalajs.sbtplugin.RuntimeDOM + +name := "Scala.js sbt test" version := scalaJSVersion @@ -12,11 +16,13 @@ val baseSettings = scalaJSSettings ++ Seq( lazy val root = project.in(file(".")).aggregate(noDOM, withDOM) lazy val noDOM = project.settings(baseSettings: _*).settings( - name := "Scala.js SBT test w/o DOM" + name := "Scala.js sbt test w/o DOM" ) lazy val withDOM = project.settings(baseSettings: _*).settings( - ScalaJSKeys.requiresDOM := true, - ScalaJSKeys.jsDependencies += - "org.webjars" % "jquery" % "1.10.2" / "jquery.js" + name := "Scala.js sbt test w/ DOM", + jsDependencies ++= Seq( + RuntimeDOM, + "org.webjars" % "jquery" % "1.10.2" / "jquery.js" + ) ) From 748782b1a7d17d0a446519d88ffd40ef392fdbb3 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 4 Nov 2014 14:43:30 +0100 Subject: [PATCH 14/84] Make PhantomJSEnv a ComJSEnv --- sbt-plugin-test/build.sbt | 14 ++ .../jetty9/src/main/resources/test.txt | 1 + sbt-plugin-test/project/Jetty9Test.scala | 84 +++++++ sbt-plugin-test/project/build.sbt | 2 + .../env/phantomjs/JettyWebsocketManager.scala | 121 ++++++++++ .../env/phantomjs/PhantomJSEnv.scala | 210 ++++++++++++++++-- .../phantomjs/PhantomJettyClassLoader.scala | 59 +++++ .../env/phantomjs/WebsocketListener.scala | 10 + .../env/phantomjs/WebsocketManager.scala | 10 + .../sbtplugin/test/env/PhantomJSTest.scala | 2 +- 10 files changed, 494 insertions(+), 19 deletions(-) create mode 100644 sbt-plugin-test/jetty9/src/main/resources/test.txt create mode 100644 sbt-plugin-test/project/Jetty9Test.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketListener.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketManager.scala diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index c1dc0fbef..6a050b42d 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -2,6 +2,8 @@ import ScalaJSKeys._ import scala.scalajs.sbtplugin.RuntimeDOM +import scala.scalajs.sbtplugin.env.phantomjs.PhantomJSEnv + name := "Scala.js sbt test" version := scalaJSVersion @@ -26,3 +28,15 @@ lazy val withDOM = project.settings(baseSettings: _*).settings( "org.webjars" % "jquery" % "1.10.2" / "jquery.js" ) ) + +lazy val jetty9 = project.settings(baseSettings: _*).settings( + name := "Scala.js sbt test with jetty9 on classpath", + jsDependencies ++= Seq( + RuntimeDOM, + "org.webjars" % "jquery" % "1.10.2" / "jquery.js" + ), + postLinkJSEnv := new PhantomJSEnv( + addArgs = List("--web-security=no"), // allow cross domain requests + jettyClassLoader = scalaJSPhantomJSClassLoader.value), + Jetty9Test.runSetting +) diff --git a/sbt-plugin-test/jetty9/src/main/resources/test.txt b/sbt-plugin-test/jetty9/src/main/resources/test.txt new file mode 100644 index 000000000..68300b856 --- /dev/null +++ b/sbt-plugin-test/jetty9/src/main/resources/test.txt @@ -0,0 +1 @@ +It works! diff --git a/sbt-plugin-test/project/Jetty9Test.scala b/sbt-plugin-test/project/Jetty9Test.scala new file mode 100644 index 000000000..0a89df1d2 --- /dev/null +++ b/sbt-plugin-test/project/Jetty9Test.scala @@ -0,0 +1,84 @@ +import sbt._ +import Keys._ + +import scala.scalajs.sbtplugin._ +import ScalaJSPlugin._ +import ScalaJSKeys._ +import Implicits._ + +import scala.scalajs.tools.env._ +import scala.scalajs.tools.io._ + +import org.eclipse.jetty.server._ +import org.eclipse.jetty.server.handler._ +import org.eclipse.jetty.util.component._ + +import java.io.File + +object Jetty9Test { + + private val jettyPort = 23548 + + val runSetting = run in fastOptStage <<= Def.inputTask { + val env = (jsEnv in fastOptStage in Compile).value.asInstanceOf[ComJSEnv] + val cp = (scalaJSExecClasspath in fastOptStage in Compile).value + val jsConsole = scalaJSConsole.value + + val code = new MemVirtualJSFile("runner.js").withContent( + """ + scalajsCom.init(function(msg) { + jQuery.ajax({ + url: msg, + success: function(dat) { + scalajsCom.send(dat.trim()); + scalajsCom.close(); + }, + error: function() { + scalajsCom.send("failed!"); + scalajsCom.close(); + } + }); + }); + """ + ) + + val runner = env.comRunner(cp, code, streams.value.log, jsConsole) + + runner.start() + + val jetty = setupJetty((resourceDirectory in Compile).value) + + jetty.addLifeCycleListener(new AbstractLifeCycle.AbstractLifeCycleListener { + override def lifeCycleStarted(event: LifeCycle): Unit = { + try { + runner.send(s"http://localhost:$jettyPort/test.txt") + val msg = runner.receive() + val expected = "It works!" + if (msg != expected) + sys.error(s"""received "$msg" instead of "$expected"""") + } finally { + runner.close() + jetty.stop() + } + } + }) + + jetty.start() + runner.await() + jetty.join() + } + + private def setupJetty(dir: File): Server = { + val server = new Server(jettyPort) + + val resource_handler = new ResourceHandler() + resource_handler.setResourceBase(dir.getAbsolutePath) + + val handlers = new HandlerList() + handlers.setHandlers(Array(resource_handler, new DefaultHandler())) + server.setHandler(handlers) + + server + } + +} diff --git a/sbt-plugin-test/project/build.sbt b/sbt-plugin-test/project/build.sbt index da8f2d32d..841928979 100644 --- a/sbt-plugin-test/project/build.sbt +++ b/sbt-plugin-test/project/build.sbt @@ -1,2 +1,4 @@ addSbtPlugin("org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % scala.scalajs.ir.ScalaJSVersions.current) + +libraryDependencies += "org.eclipse.jetty" % "jetty-server" % "9.2.3.v20140905" diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala new file mode 100644 index 000000000..e13c97b2a --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala @@ -0,0 +1,121 @@ +package scala.scalajs.sbtplugin.env.phantomjs + +import javax.servlet.http.HttpServletRequest + +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.server.nio.SelectChannelConnector +import org.eclipse.jetty.websocket.{WebSocket, WebSocketHandler} +import org.eclipse.jetty.util.component.{LifeCycle, AbstractLifeCycle} +import org.eclipse.jetty.util.log + +private[phantomjs] final class JettyWebsocketManager( + wsListener: WebsocketListener) extends WebsocketManager { thisMgr => + + private[this] var webSocketConn: WebSocket.Connection = null + private[this] var closed = false + + // We can just set the logger here, since we are supposed to be protected by + // the private ClassLoader that loads us reflectively. + log.Log.setLog(new WSLogger("root")) + + private[this] val connector = new SelectChannelConnector + + connector.setHost("localhost") + connector.setPort(0) + + private[this] val server = new Server() + + server.addConnector(connector) + server.setHandler(new WebSocketHandler { + // Support Hixie 76 for Phantom.js + getWebSocketFactory().setMinVersion(-1) + + override def doWebSocketConnect( + request: HttpServletRequest, protocol: String): WebSocket = + new ComWebSocketListener + }) + + server.addLifeCycleListener(new AbstractLifeCycle.AbstractLifeCycleListener { + override def lifeCycleStarted(event: LifeCycle): Unit = { + if (event.isRunning()) + wsListener.onRunning() + } + }) + + private class ComWebSocketListener extends WebSocket.OnTextMessage { + override def onOpen(connection: WebSocket.Connection): Unit = { + thisMgr.synchronized { + if (isConnected) + throw new IllegalStateException("Client connected twice") + webSocketConn = connection + } + wsListener.onOpen() + } + + override def onClose(statusCode: Int, reason: String): Unit = { + thisMgr.synchronized { + webSocketConn = null + closed = true + } + wsListener.onClose() + server.stop() + } + + override def onMessage(message: String): Unit = + wsListener.onMessage(message) + } + + private class WSLogger(fullName: String) extends log.AbstractLogger { + private[this] var debugEnabled = false + + def debug(msg: String, args: Object*): Unit = + if (debugEnabled) log("DEBUG", msg, args) + + def debug(msg: String, thrown: Throwable): Unit = + if (debugEnabled) log("DEBUG", msg, thrown) + + def debug(thrown: Throwable): Unit = + if (debugEnabled) log("DEBUG", thrown) + + def getName(): String = fullName + + def ignore(ignored: Throwable): Unit = () + + def info(msg: String, args: Object*): Unit = log("INFO", msg, args) + def info(msg: String, thrown: Throwable): Unit = log("INFO", msg, thrown) + def info(thrown: Throwable): Unit = log("INFO", thrown) + + def warn(msg: String, args: Object*): Unit = log("WARN", msg, args) + def warn(msg: String, thrown: Throwable): Unit = log("WARN", msg, thrown) + def warn(thrown: Throwable): Unit = log("WARN", thrown) + + def isDebugEnabled(): Boolean = debugEnabled + def setDebugEnabled(enabled: Boolean): Unit = debugEnabled = enabled + + private def log(lvl: String, msg: String, args: Object*): Unit = + wsListener.log(s"$lvl: $msg " + args.mkString(", ")) + + private def log(lvl: String, msg: String, thrown: Throwable): Unit = + wsListener.log(s"$lvl: $msg $thrown\n{$thrown.getStackStrace}") + + private def log(lvl: String, thrown: Throwable): Unit = + wsListener.log(s"$lvl: $thrown\n{$thrown.getStackStrace}") + + protected def newLogger(fullName: String) = new WSLogger(fullName) + } + + def start(): Unit = server.start() + + def stop(): Unit = server.stop() + + def isConnected: Boolean = webSocketConn != null && !closed + def isClosed: Boolean = closed + + def localPort: Int = connector.getLocalPort() + + def sendMessage(msg: String) = synchronized { + if (webSocketConn != null) + webSocketConn.sendMessage(msg) + } + +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala index 0fb649a81..17f18a34a 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -19,14 +19,18 @@ import scala.scalajs.tools.logging._ import scala.scalajs.sbtplugin.JSUtils._ import java.io.{ Console => _, _ } +import java.net._ + import scala.io.Source +import scala.collection.mutable class PhantomJSEnv( phantomjsPath: String = "phantomjs", addArgs: Seq[String] = Seq.empty, addEnv: Map[String, String] = Map.empty, - val autoExit: Boolean = true -) extends ExternalJSEnv(addArgs, addEnv) { + val autoExit: Boolean = true, + jettyClassLoader: ClassLoader = getClass().getClassLoader() +) extends ExternalJSEnv(addArgs, addEnv) with ComJSEnv { protected def vmName: String = "PhantomJS" protected def executable: String = phantomjsPath @@ -41,16 +45,171 @@ class PhantomJSEnv( new AsyncPhantomRunner(classpath, code, logger, console) } + override def comRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): ComJSRunner = { + new ComPhantomRunner(classpath, code, logger, console) + } + protected class PhantomRunner(classpath: CompleteClasspath, - code: VirtualJSFile, logger: Logger, console: JSConsole + code: VirtualJSFile, logger: Logger, console: JSConsole ) extends ExtRunner(classpath, code, logger, console) with AbstractPhantomRunner protected class AsyncPhantomRunner(classpath: CompleteClasspath, - code: VirtualJSFile, logger: Logger, console: JSConsole + code: VirtualJSFile, logger: Logger, console: JSConsole ) extends AsyncExtRunner(classpath, code, logger, console) with AbstractPhantomRunner + protected class ComPhantomRunner(classpath: CompleteClasspath, + code: VirtualJSFile, logger: Logger, console: JSConsole + ) extends AsyncPhantomRunner(classpath, code, logger, console) + with ComJSRunner with WebsocketListener { + + private def loadMgr() = { + val clazz = jettyClassLoader.loadClass( + "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager") + + val ctors = clazz.getConstructors() + assert(ctors.length == 1, "JettyWebsocketManager may only have one ctor") + + val mgr = ctors.head.newInstance(this) + + mgr.asInstanceOf[WebsocketManager] + } + + val mgr: WebsocketManager = loadMgr() + + def onRunning(): Unit = synchronized(notifyAll()) + def onOpen(): Unit = synchronized(notifyAll()) + def onClose(): Unit = synchronized(notifyAll()) + + def onMessage(msg: String): Unit = synchronized { + recvBuf.enqueue(msg) + notifyAll() + } + + def log(msg: String): Unit = logger.debug(s"PhantomJS WS Jetty: $msg") + + mgr.start() + + private[this] val recvBuf = mutable.Queue.empty[String] + + /** The websocket server starts asynchronously, but we need the port it is + * running on. This method waits until the port is non-negative and + * returns its value. + */ + private def waitForPort(): Int = { + while (mgr.localPort < 0) + wait() + mgr.localPort + } + + private def comSetup = { + def maybeExit(code: Int) = + if (autoExit) + s"window.callPhantom({ action: 'exit', returnValue: $code });" + else + "" + + val serverPort = waitForPort() + + val code = s""" + |(function() { + | // The socket for communication + | var websocket = null; + | + | // Buffer for messages sent before socket is open + | var outMsgBuf = null; + | + | window.scalajsCom = { + | init: function(recvCB) { + | if (websocket !== null) throw new Error("Com already open"); + | + | outMsgBuf = []; + | + | websocket = new WebSocket("ws://localhost:$serverPort"); + | + | websocket.onopen = function(evt) { + | for (var i = 0; i < outMsgBuf.length; ++i) + | websocket.send(outMsgBuf[i]); + | outMsgBuf = null; + | }; + | websocket.onclose = function(evt) { + | websocket = null; + | ${maybeExit(0)} + | }; + | websocket.onmessage = function(evt) { + | recvCB(evt.data); + | }; + | websocket.onerror = function(evt) { + | websocket = null; + | ${maybeExit(-1)} + | }; + | + | // Take over responsibility to auto exit + | window.callPhantom({ + | action: 'setAutoExit', + | autoExit: false + | }); + | }, + | send: function(msg) { + | if (websocket === null) + | return; // we are closed already. ignore message + | + | if (outMsgBuf !== null) + | outMsgBuf.push(msg); + | else + | websocket.send(msg); + | }, + | close: function() { + | if (websocket === null) + | return; // we are closed already. all is well. + | + | if (outMsgBuf !== null) + | // Reschedule ourselves to give onopen a chance to kick in + | window.setTimeout(window.scalajsCom.close, 10); + | else + | websocket.close(); + | } + | } + |}).call(this);""".stripMargin + + new MemVirtualJSFile("comSetup.js").withContent(code) + } + + def send(msg: String): Unit = synchronized { + if (awaitConnection()) + mgr.sendMessage(msg) + } + + def receive(): String = synchronized { + if (recvBuf.isEmpty && !awaitConnection()) + throw new ComJSEnv.ComClosedException + while (recvBuf.isEmpty && !mgr.isClosed) + wait() + + if (recvBuf.isEmpty) + throw new ComJSEnv.ComClosedException + else + recvBuf.dequeue() + } + + def close(): Unit = mgr.stop() + + /** Waits until the JS VM has established a connection, or the VM + * terminated. Returns true if a connection was established. + */ + private def awaitConnection(): Boolean = { + while (!mgr.isConnected && !mgr.isClosed && isRunning) + wait(200) // We sleep-wait for isRunning + + mgr.isConnected + } + + override protected def initFiles(): Seq[VirtualJSFile] = + super.initFiles :+ comSetup + } + protected trait AbstractPhantomRunner extends AbstractExtRunner { override protected def getVMArgs() = @@ -122,7 +281,12 @@ class PhantomJSEnv( new MemVirtualJSFile("scalaJSEnvInfo.js").withContent( """ |__ScalaJSEnv = { - | exitFunction: function(status) { window.callPhantom(status); } + | exitFunction: function(status) { + | window.callPhantom({ + | action: 'exit', + | returnValue: status | 0 + | }); + | } |}; """.stripMargin ) @@ -148,6 +312,7 @@ class PhantomJSEnv( s"""// Scala.js Phantom.js launcher |var page = require('webpage').create(); |var url = ${toJSstr(webF.getAbsolutePath)}; + |var autoExit = $autoExit; |page.onConsoleMessage = function(msg) { | console.log(msg); |}; @@ -162,25 +327,34 @@ class PhantomJSEnv( | | phantom.exit(2); |}; - |page.onCallback = function(status) { - | phantom.exit(status); + |page.onCallback = function(data) { + | if (!data.action) { + | console.error('Called callback without action'); + | phantom.exit(3); + | } else if (data.action === 'exit') { + | phantom.exit(data.returnValue || 0); + | } else if (data.action === 'setAutoExit') { + | if (typeof(data.autoExit) === 'boolean') + | autoExit = data.autoExit; + | else + | autoExit = true; + | } else { + | console.error('Unknown callback action ' + data.action); + | phantom.exit(4); + | } |}; + |page.open(url, function (status) { + | if (autoExit || status !== 'success') + | phantom.exit(status !== 'success'); + |}); |""".stripMargin) - if (autoExit) { - out.write(""" - page.open(url, function (status) { - phantom.exit(status != 'success'); - });""") - } else { - out.write(""" - page.open(url, function (status) { - if (status != 'success') phantom.exit(1); - });""") - } } finally { out.close() } + logger.debug( + "PhantomJS using launcher at: " + launcherTmpF.getAbsolutePath()) + launcherTmpF } diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala new file mode 100644 index 000000000..d5310aa98 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala @@ -0,0 +1,59 @@ +package scala.scalajs.sbtplugin.env.phantomjs + +import scala.scalajs.tools.io.IO + +/** A special [[ClassLoader]] to load the Jetty 8 dependency of [[PhantomJSEnv]] + * in a private space. + * + * It loads everything that belongs to [[JettyWebsocketManager]] itself (while + * retrieving the requested class file from its parent. + * For all other classes, it first tries to load them from [[jettyLoader]], + * which should only contain the Jetty 8 classpath. + * If this fails, it delegates to its parent. + * + * The rationale is, that [[JettyWebsocketManager]] and its dependees can use + * the classes on the Jetty 8 classpath, while they remain hidden from the rest + * of the Java world. This allows to load another version of Jetty in the same + * JVM for the rest of the project. + */ +private[sbtplugin] class PhantomJettyClassLoader(jettyLoader: ClassLoader, + parent: ClassLoader) extends ClassLoader(parent) { + + def this(loader: ClassLoader) = + this(loader, ClassLoader.getSystemClassLoader()) + + /** Classes needed to bridge private jetty classpath and public PhantomJS + * Basically everything defined in JettyWebsocketManager. + */ + private val bridgeClasses = Set( + "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager", + "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$WSLogger", + "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$ComWebSocketListener", + "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$$anon$1", + "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$$anon$2" + ) + + override protected def loadClass(name: String, resolve: Boolean): Class[_] = { + if (bridgeClasses.contains(name)) { + // Load bridgeClasses manually since they must be associated to this + // class loader, rather than the parent class loader in order to find the + // jetty classes + val wsManager = + parent.getResourceAsStream(name.replace('.', '/') + ".class") + + if (wsManager == null) { + throw new ClassNotFoundException(name) + } else { + val buf = IO.readInputStreamToByteArray(wsManager) + defineClass(name, buf, 0, buf.length) + } + } else { + try { + jettyLoader.loadClass(name) + } catch { + case _: ClassNotFoundException => + super.loadClass(name, resolve) + } + } + } +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketListener.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketListener.scala new file mode 100644 index 000000000..4faac64c1 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketListener.scala @@ -0,0 +1,10 @@ +package scala.scalajs.sbtplugin.env.phantomjs + +private[phantomjs] trait WebsocketListener { + def onRunning(): Unit + def onOpen(): Unit + def onClose(): Unit + def onMessage(msg: String): Unit + + def log(msg: String): Unit +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketManager.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketManager.scala new file mode 100644 index 000000000..a4668417d --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketManager.scala @@ -0,0 +1,10 @@ +package scala.scalajs.sbtplugin.env.phantomjs + +private[phantomjs] trait WebsocketManager { + def start(): Unit + def stop(): Unit + def sendMessage(msg: String): Unit + def localPort: Int + def isConnected: Boolean + def isClosed: Boolean +} diff --git a/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala b/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala index bdb33e57d..23e240d4e 100644 --- a/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala +++ b/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala @@ -4,7 +4,7 @@ import scala.scalajs.sbtplugin.env.phantomjs.PhantomJSEnv import org.junit.Test -class PhantomJSTest extends JSEnvTest { +class PhantomJSTest extends JSEnvTest with ComTests { protected def newJSEnv = new PhantomJSEnv From 9c2c87df0b3e4188776fbf945454040aba79b97e Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 14 Nov 2014 15:42:30 +0100 Subject: [PATCH 15/84] Fix scala-js/scala-js#1266: Proper connection closing on Node.js ComJSEnv --- .../scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala index 17f18a34a..7e700999d 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -196,6 +196,11 @@ class PhantomJSEnv( def close(): Unit = mgr.stop() + override def stop(): Unit = { + close() + super.stop() + } + /** Waits until the JS VM has established a connection, or the VM * terminated. Returns true if a connection was established. */ From 65188be53259ed12681e31485137470f35fd0348 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 21 Nov 2014 08:53:09 +0100 Subject: [PATCH 16/84] Fix scala-js/scala-js#1288: Make stage a setting in sbt plugin --- sbt-plugin-test/project/Jetty9Test.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sbt-plugin-test/project/Jetty9Test.scala b/sbt-plugin-test/project/Jetty9Test.scala index 0a89df1d2..ec609967c 100644 --- a/sbt-plugin-test/project/Jetty9Test.scala +++ b/sbt-plugin-test/project/Jetty9Test.scala @@ -19,9 +19,9 @@ object Jetty9Test { private val jettyPort = 23548 - val runSetting = run in fastOptStage <<= Def.inputTask { - val env = (jsEnv in fastOptStage in Compile).value.asInstanceOf[ComJSEnv] - val cp = (scalaJSExecClasspath in fastOptStage in Compile).value + val runSetting = run <<= Def.inputTask { + val env = (jsEnv in Compile).value.asInstanceOf[ComJSEnv] + val cp = (scalaJSExecClasspath in Compile).value val jsConsole = scalaJSConsole.value val code = new MemVirtualJSFile("runner.js").withContent( From 6dd414a15eff070f175a1d328cb1157855852db4 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 18 Nov 2014 07:03:18 +0100 Subject: [PATCH 17/84] Fix scala-js/scala-js#1282: Don't load same class twice in PhantomJettyClassLoader --- .../phantomjs/PhantomJettyClassLoader.scala | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala index d5310aa98..02c229ba2 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala @@ -38,14 +38,18 @@ private[sbtplugin] class PhantomJettyClassLoader(jettyLoader: ClassLoader, // Load bridgeClasses manually since they must be associated to this // class loader, rather than the parent class loader in order to find the // jetty classes - val wsManager = - parent.getResourceAsStream(name.replace('.', '/') + ".class") - if (wsManager == null) { - throw new ClassNotFoundException(name) - } else { - val buf = IO.readInputStreamToByteArray(wsManager) - defineClass(name, buf, 0, buf.length) + // First check if we have loaded it already + Option(findLoadedClass(name)) getOrElse { + val wsManager = + parent.getResourceAsStream(name.replace('.', '/') + ".class") + + if (wsManager == null) { + throw new ClassNotFoundException(name) + } else { + val buf = IO.readInputStreamToByteArray(wsManager) + defineClass(name, buf, 0, buf.length) + } } } else { try { From be34887058fe56f38ba7230aa5936e5b09a3c7c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 24 Nov 2014 16:26:37 +0100 Subject: [PATCH 18/84] Fix scala-js/scala-js#1025: Turn the sbt plugin into an AutoPlugin. Note that we cannot use the AutoPlugin in our build itself, because we need the *abstract* set of settings. However, the AutoPlugin is tested in sbt-plugin-test. --- sbt-plugin-test/build.sbt | 53 +++++++++++++----------- sbt-plugin-test/project/Jetty9Test.scala | 3 +- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 6a050b42d..6ff0b2e1b 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -1,5 +1,3 @@ -import ScalaJSKeys._ - import scala.scalajs.sbtplugin.RuntimeDOM import scala.scalajs.sbtplugin.env.phantomjs.PhantomJSEnv @@ -8,35 +6,42 @@ name := "Scala.js sbt test" version := scalaJSVersion -val baseSettings = scalaJSSettings ++ Seq( +val baseSettings = Seq( version := scalaJSVersion, scalaVersion := "2.11.2", libraryDependencies += "org.scala-lang.modules.scalajs" %% "scalajs-jasmine-test-framework" % scalaJSVersion % "test" ) -lazy val root = project.in(file(".")).aggregate(noDOM, withDOM) +lazy val root = project.in(file(".")). + aggregate(noDOM, withDOM) -lazy val noDOM = project.settings(baseSettings: _*).settings( - name := "Scala.js sbt test w/o DOM" -) +lazy val noDOM = project.settings(baseSettings: _*). + enablePlugins(ScalaJSPlugin). + settings( + name := "Scala.js sbt test w/o DOM" + ) -lazy val withDOM = project.settings(baseSettings: _*).settings( - name := "Scala.js sbt test w/ DOM", - jsDependencies ++= Seq( - RuntimeDOM, - "org.webjars" % "jquery" % "1.10.2" / "jquery.js" +lazy val withDOM = project.settings(baseSettings: _*). + enablePlugins(ScalaJSPlugin). + settings( + name := "Scala.js sbt test w/ DOM", + jsDependencies ++= Seq( + RuntimeDOM, + "org.webjars" % "jquery" % "1.10.2" / "jquery.js" + ) ) -) -lazy val jetty9 = project.settings(baseSettings: _*).settings( - name := "Scala.js sbt test with jetty9 on classpath", - jsDependencies ++= Seq( - RuntimeDOM, - "org.webjars" % "jquery" % "1.10.2" / "jquery.js" - ), - postLinkJSEnv := new PhantomJSEnv( - addArgs = List("--web-security=no"), // allow cross domain requests - jettyClassLoader = scalaJSPhantomJSClassLoader.value), - Jetty9Test.runSetting -) +lazy val jetty9 = project.settings(baseSettings: _*). + enablePlugins(ScalaJSPlugin). + settings( + name := "Scala.js sbt test with jetty9 on classpath", + jsDependencies ++= Seq( + RuntimeDOM, + "org.webjars" % "jquery" % "1.10.2" / "jquery.js" + ), + postLinkJSEnv := new PhantomJSEnv( + addArgs = List("--web-security=no"), // allow cross domain requests + jettyClassLoader = scalaJSPhantomJSClassLoader.value), + Jetty9Test.runSetting + ) diff --git a/sbt-plugin-test/project/Jetty9Test.scala b/sbt-plugin-test/project/Jetty9Test.scala index ec609967c..6a84114e2 100644 --- a/sbt-plugin-test/project/Jetty9Test.scala +++ b/sbt-plugin-test/project/Jetty9Test.scala @@ -2,8 +2,7 @@ import sbt._ import Keys._ import scala.scalajs.sbtplugin._ -import ScalaJSPlugin._ -import ScalaJSKeys._ +import ScalaJSPlugin.autoImport._ import Implicits._ import scala.scalajs.tools.env._ From cbf8a9d211eee06a39b6680959d1ebc58837f3fb Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 25 Nov 2014 10:27:15 +0100 Subject: [PATCH 19/84] Throw more aggressively on PhantomJSCom failure --- .../sbtplugin/env/phantomjs/JettyWebsocketManager.scala | 5 +++++ .../scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala index e13c97b2a..3dec79c3b 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala @@ -59,6 +59,11 @@ private[phantomjs] final class JettyWebsocketManager( } wsListener.onClose() server.stop() + + if (statusCode != 1000) { + throw new Exception("Abnormal closing of connection. " + + s"Code: $statusCode, Reason: $reason") + } } override def onMessage(message: String): Unit = diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala index 7e700999d..1368eb550 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -143,7 +143,7 @@ class PhantomJSEnv( | }; | websocket.onerror = function(evt) { | websocket = null; - | ${maybeExit(-1)} + | throw new Error("Websocket failed: " + evt); | }; | | // Take over responsibility to auto exit From 6d61185d28c53094e4d40266e48ebc2899a94994 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 25 Nov 2014 10:27:40 +0100 Subject: [PATCH 20/84] Fix initialization race in PhantomJSEnv --- .../scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala index 1368eb550..ec0b4c91d 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -90,10 +90,10 @@ class PhantomJSEnv( def log(msg: String): Unit = logger.debug(s"PhantomJS WS Jetty: $msg") - mgr.start() - private[this] val recvBuf = mutable.Queue.empty[String] + mgr.start() + /** The websocket server starts asynchronously, but we need the port it is * running on. This method waits until the port is non-negative and * returns its value. From 33a3b17b9d1bd96015fdd4d55fb17fabe39e94f6 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 25 Nov 2014 10:31:12 +0100 Subject: [PATCH 21/84] Fix scala-js/scala-js#1304: Fragment messages to limit total size to 32KB --- .../env/phantomjs/PhantomJSEnv.scala | 78 +++++++++++++++++-- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala index ec0b4c91d..7bb47d246 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -23,6 +23,7 @@ import java.net._ import scala.io.Source import scala.collection.mutable +import scala.annotation.tailrec class PhantomJSEnv( phantomjsPath: String = "phantomjs", @@ -32,6 +33,8 @@ class PhantomJSEnv( jettyClassLoader: ClassLoader = getClass().getClassLoader() ) extends ExternalJSEnv(addArgs, addEnv) with ComJSEnv { + import PhantomJSEnv._ + protected def vmName: String = "PhantomJS" protected def executable: String = phantomjsPath @@ -115,12 +118,42 @@ class PhantomJSEnv( val code = s""" |(function() { + | var MaxPayloadSize = $MaxCharPayloadSize; + | | // The socket for communication | var websocket = null; | | // Buffer for messages sent before socket is open | var outMsgBuf = null; | + | function sendImpl(msg) { + | var frags = (msg.length / MaxPayloadSize) | 0; + | + | for (var i = 0; i < frags; ++i) { + | var payload = msg.substring( + | i * MaxPayloadSize, (i + 1) * MaxPayloadSize); + | websocket.send("1" + payload); + | } + | + | websocket.send("0" + msg.substring(frags * MaxPayloadSize)); + | } + | + | function recvImpl(recvCB) { + | var recvBuf = ""; + | + | return function(evt) { + | var newData = recvBuf + evt.data.substring(1); + | if (evt.data.charAt(0) == "0") { + | recvBuf = ""; + | recvCB(newData); + | } else if (evt.data.charAt(0) == "1") { + | recvBuf = newData; + | } else { + | throw new Error("Bad fragmentation flag in " + evt.data); + | } + | }; + | } + | | window.scalajsCom = { | init: function(recvCB) { | if (websocket !== null) throw new Error("Com already open"); @@ -131,16 +164,14 @@ class PhantomJSEnv( | | websocket.onopen = function(evt) { | for (var i = 0; i < outMsgBuf.length; ++i) - | websocket.send(outMsgBuf[i]); + | sendImpl(outMsgBuf[i]); | outMsgBuf = null; | }; | websocket.onclose = function(evt) { | websocket = null; | ${maybeExit(0)} | }; - | websocket.onmessage = function(evt) { - | recvCB(evt.data); - | }; + | websocket.onmessage = recvImpl(recvCB); | websocket.onerror = function(evt) { | websocket = null; | throw new Error("Websocket failed: " + evt); @@ -159,7 +190,7 @@ class PhantomJSEnv( | if (outMsgBuf !== null) | outMsgBuf.push(msg); | else - | websocket.send(msg); + | sendImpl(msg); | }, | close: function() { | if (websocket === null) @@ -178,13 +209,40 @@ class PhantomJSEnv( } def send(msg: String): Unit = synchronized { - if (awaitConnection()) - mgr.sendMessage(msg) + if (awaitConnection()) { + val fragParts = msg.length / MaxCharPayloadSize + + for (i <- 0 until fragParts) { + val payload = msg.substring( + i * MaxCharPayloadSize, (i + 1) * MaxCharPayloadSize) + mgr.sendMessage("1" + payload) + } + + mgr.sendMessage("0" + msg.substring(fragParts * MaxCharPayloadSize)) + } } def receive(): String = synchronized { if (recvBuf.isEmpty && !awaitConnection()) throw new ComJSEnv.ComClosedException + + @tailrec + def loop(acc: String): String = { + val frag = receiveFrag() + val newAcc = acc + frag.substring(1) + + if (frag(0) == '0') + newAcc + else if (frag(0) == '1') + loop(newAcc) + else + throw new AssertionError("Bad fragmentation flag in " + frag) + } + + loop("") + } + + private def receiveFrag(): String = { while (recvBuf.isEmpty && !mgr.isClosed) wait() @@ -400,3 +458,9 @@ class PhantomJSEnv( } } + +object PhantomJSEnv { + private final val MaxByteMessageSize = 32768 // 32 KB + private final val MaxCharMessageSize = MaxByteMessageSize / 2 // 2B per char + private final val MaxCharPayloadSize = MaxCharMessageSize - 1 // frag flag +} From 4901b4fe12948e6a7004919e014a49595d3af21d Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 25 Nov 2014 17:34:34 +0100 Subject: [PATCH 22/84] Add factories to sbt plugin for PhantomJSEnv and NodeJSEnv This hopefully simplifies build definitions that need to tweak some command line options or just force a given environment. At the same time, this also fixes scala-js/scala-js#1302 (use proper class loader for PhantomJSEnv in our CI). --- sbt-plugin-test/build.sbt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 6ff0b2e1b..dd2e12eb9 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -1,7 +1,5 @@ import scala.scalajs.sbtplugin.RuntimeDOM -import scala.scalajs.sbtplugin.env.phantomjs.PhantomJSEnv - name := "Scala.js sbt test" version := scalaJSVersion @@ -40,8 +38,7 @@ lazy val jetty9 = project.settings(baseSettings: _*). RuntimeDOM, "org.webjars" % "jquery" % "1.10.2" / "jquery.js" ), - postLinkJSEnv := new PhantomJSEnv( - addArgs = List("--web-security=no"), // allow cross domain requests - jettyClassLoader = scalaJSPhantomJSClassLoader.value), + // Use PhantomJS, allow cross domain requests + postLinkJSEnv := PhantomJSEnv(args = Seq("--web-security=no")).value, Jetty9Test.runSetting ) From 0b1045d10e4b7bec44a6c15d50f3aa91941885f0 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 25 Nov 2014 19:10:59 +0100 Subject: [PATCH 23/84] Fix scala-js/scala-js#1308: Auto import RuntimeDOM builder Rename former RuntimeDOM to RuntimeDOMDep to avoid import conflicts. --- sbt-plugin-test/build.sbt | 2 -- 1 file changed, 2 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index dd2e12eb9..48a3f74bc 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -1,5 +1,3 @@ -import scala.scalajs.sbtplugin.RuntimeDOM - name := "Scala.js sbt test" version := scalaJSVersion From 830d7eec6aded3d5f9892e7f0fc10e3bad72d141 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Wed, 26 Nov 2014 14:47:08 +0100 Subject: [PATCH 24/84] Centralize overriding of stop in ComJSEnv --- .../scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala index 7bb47d246..5139face0 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -254,11 +254,6 @@ class PhantomJSEnv( def close(): Unit = mgr.stop() - override def stop(): Unit = { - close() - super.stop() - } - /** Waits until the JS VM has established a connection, or the VM * terminated. Returns true if a connection was established. */ From 590bdc1644f3ecfdefe5e3ea62864edc41b0b6bd Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Thu, 27 Nov 2014 07:13:56 +0100 Subject: [PATCH 25/84] Improve information in thrown ComClosedExceptions --- .../scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala index 5139face0..449858cc5 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -224,7 +224,7 @@ class PhantomJSEnv( def receive(): String = synchronized { if (recvBuf.isEmpty && !awaitConnection()) - throw new ComJSEnv.ComClosedException + throw new ComJSEnv.ComClosedException("Phantom.js isn't connected") @tailrec def loop(acc: String): String = { From fbac5a44f3aed67f6d69afa98f9bb61dd86be289 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Thu, 20 Nov 2014 15:37:05 +0100 Subject: [PATCH 26/84] Fix scala-js/scala-js#1073: New test framework interface (aligned with sbt) --- sbt-plugin-test/build.sbt | 61 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 6ff0b2e1b..66007d8c3 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -6,15 +6,18 @@ name := "Scala.js sbt test" version := scalaJSVersion -val baseSettings = Seq( +val versionSettings = Seq( version := scalaJSVersion, - scalaVersion := "2.11.2", + scalaVersion := "2.11.2" +) + +val baseSettings = versionSettings ++ Seq( libraryDependencies += "org.scala-lang.modules.scalajs" %% "scalajs-jasmine-test-framework" % scalaJSVersion % "test" ) lazy val root = project.in(file(".")). - aggregate(noDOM, withDOM) + aggregate(noDOM, withDOM, multiTestJS, multiTestJVM) lazy val noDOM = project.settings(baseSettings: _*). enablePlugins(ScalaJSPlugin). @@ -45,3 +48,55 @@ lazy val jetty9 = project.settings(baseSettings: _*). jettyClassLoader = scalaJSPhantomJSClassLoader.value), Jetty9Test.runSetting ) + +val testFrameworkSettings = Seq( + name := "Dummy cross JS/JVM test framework", + unmanagedSourceDirectories in Compile += + baseDirectory.value / ".." / "src" / "main" / "scala" +) + +lazy val testFrameworkJS = project.in(file("testFramework/.js")). + enablePlugins(ScalaJSPlugin). + settings(versionSettings: _*). + settings(testFrameworkSettings: _*). + settings( + libraryDependencies += + "org.scala-lang.modules.scalajs" %% "scalajs-test-interface" % scalaJSVersion + ) + +lazy val testFrameworkJVM = project.in(file("testFramework/.jvm")). + settings(versionSettings: _*). + settings(testFrameworkSettings: _*). + settings( + libraryDependencies ++= Seq( + "org.scala-sbt" % "test-interface" % "1.0", + "org.scala-lang.modules.scalajs" %% "scalajs-stubs" % scalaJSVersion % "provided" + ) + ) + +val multiTestSettings = Seq( + testFrameworks ++= Seq( + TestFramework("sbttest.framework.DummyFramework"), + TestFramework("inexistent.Foo", "another.strange.Bar") + ), + unmanagedSourceDirectories in Compile += + baseDirectory.value / ".." / "shared" / "src" / "main" / "scala", + unmanagedSourceDirectories in Test += + baseDirectory.value / ".." / "shared" / "src" / "test" / "scala" +) + +lazy val multiTestJS = project.in(file("multiTest/js")). + enablePlugins(ScalaJSPlugin). + settings(baseSettings: _*). + settings(multiTestSettings: _*). + settings(name := "Multi test framework test JS"). + dependsOn(testFrameworkJS % "test") + +lazy val multiTestJVM = project.in(file("multiTest/jvm")). + settings(versionSettings: _*). + settings(multiTestSettings: _*). + settings( + name := "Multi test framework test JVM", + libraryDependencies += "com.novocode" % "junit-interface" % "0.9" % "test" + ). + dependsOn(testFrameworkJVM % "test") From 4519ba6fd2ea986417178bdea2cdee88b952f26a Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Thu, 27 Nov 2014 16:48:23 +0100 Subject: [PATCH 27/84] Fix scala-js/scala-js#1319: Use correct default class loader in PhantomJSEnv --- .../scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala index 449858cc5..b1ba865c1 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -30,7 +30,7 @@ class PhantomJSEnv( addArgs: Seq[String] = Seq.empty, addEnv: Map[String, String] = Map.empty, val autoExit: Boolean = true, - jettyClassLoader: ClassLoader = getClass().getClassLoader() + jettyClassLoader: ClassLoader = null ) extends ExternalJSEnv(addArgs, addEnv) with ComJSEnv { import PhantomJSEnv._ @@ -69,7 +69,11 @@ class PhantomJSEnv( with ComJSRunner with WebsocketListener { private def loadMgr() = { - val clazz = jettyClassLoader.loadClass( + val loader = + if (jettyClassLoader != null) jettyClassLoader + else getClass().getClassLoader() + + val clazz = loader.loadClass( "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager") val ctors = clazz.getConstructors() From 308f5cde3ef58d05d84707a90e91d7f77a44a7e2 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Thu, 27 Nov 2014 15:50:57 +0100 Subject: [PATCH 28/84] Remove sbtplugin.JSUtils --- .../scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala index b1ba865c1..886b433dd 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala @@ -11,13 +11,13 @@ package scala.scalajs.sbtplugin.env.phantomjs import scala.scalajs.sbtplugin.env._ +import scala.scalajs.ir.Utils.escapeJS + import scala.scalajs.tools.io._ import scala.scalajs.tools.classpath._ import scala.scalajs.tools.env._ import scala.scalajs.tools.logging._ -import scala.scalajs.sbtplugin.JSUtils._ - import java.io.{ Console => _, _ } import java.net._ @@ -373,7 +373,7 @@ class PhantomJSEnv( out.write( s"""// Scala.js Phantom.js launcher |var page = require('webpage').create(); - |var url = ${toJSstr(webF.getAbsolutePath)}; + |var url = "${escapeJS(webF.getAbsolutePath)}"; |var autoExit = $autoExit; |page.onConsoleMessage = function(msg) { | console.log(msg); From e429d257dd9cc11b0be4223d5ff879736ab6c1c1 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Thu, 27 Nov 2014 09:22:34 +0100 Subject: [PATCH 29/84] Fix scala-js/scala-js#1287: Reorganize package names and artifacts --- .../jsenv}/phantomjs/JettyWebsocketManager.scala | 4 ++-- .../scalajs/jsenv}/phantomjs/PhantomJSEnv.scala | 15 +++++++-------- .../phantomjs/PhantomJettyClassLoader.scala | 16 ++++++++-------- .../jsenv}/phantomjs/WebsocketListener.scala | 2 +- .../jsenv}/phantomjs/WebsocketManager.scala | 2 +- .../org/scalajs/jsenv/test}/PhantomJSTest.scala | 4 ++-- sbt-plugin-test/build.sbt | 6 +++--- sbt-plugin-test/project/Jetty9Test.scala | 6 +++--- sbt-plugin-test/project/build.sbt | 4 ++-- 9 files changed, 29 insertions(+), 30 deletions(-) rename {sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env => js-envs/src/main/scala/org/scalajs/jsenv}/phantomjs/JettyWebsocketManager.scala (97%) rename {sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env => js-envs/src/main/scala/org/scalajs/jsenv}/phantomjs/PhantomJSEnv.scala (97%) rename {sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env => js-envs/src/main/scala/org/scalajs/jsenv}/phantomjs/PhantomJettyClassLoader.scala (78%) rename {sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env => js-envs/src/main/scala/org/scalajs/jsenv}/phantomjs/WebsocketListener.scala (79%) rename {sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env => js-envs/src/main/scala/org/scalajs/jsenv}/phantomjs/WebsocketManager.scala (80%) rename {sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env => js-envs/src/test/scala/org/scalajs/jsenv/test}/PhantomJSTest.scala (68%) diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala similarity index 97% rename from sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala rename to js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala index 3dec79c3b..f4b05b53b 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala @@ -1,4 +1,4 @@ -package scala.scalajs.sbtplugin.env.phantomjs +package org.scalajs.jsenv.phantomjs import javax.servlet.http.HttpServletRequest @@ -8,7 +8,7 @@ import org.eclipse.jetty.websocket.{WebSocket, WebSocketHandler} import org.eclipse.jetty.util.component.{LifeCycle, AbstractLifeCycle} import org.eclipse.jetty.util.log -private[phantomjs] final class JettyWebsocketManager( +final class JettyWebsocketManager( wsListener: WebsocketListener) extends WebsocketManager { thisMgr => private[this] var webSocketConn: WebSocket.Connection = null diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala similarity index 97% rename from sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala rename to js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index 886b433dd..c547c485a 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -7,16 +7,15 @@ \* */ -package scala.scalajs.sbtplugin.env.phantomjs +package org.scalajs.jsenv.phantomjs -import scala.scalajs.sbtplugin.env._ +import org.scalajs.jsenv._ -import scala.scalajs.ir.Utils.escapeJS +import org.scalajs.core.ir.Utils.escapeJS -import scala.scalajs.tools.io._ -import scala.scalajs.tools.classpath._ -import scala.scalajs.tools.env._ -import scala.scalajs.tools.logging._ +import org.scalajs.core.tools.io._ +import org.scalajs.core.tools.classpath._ +import org.scalajs.core.tools.logging._ import java.io.{ Console => _, _ } import java.net._ @@ -74,7 +73,7 @@ class PhantomJSEnv( else getClass().getClassLoader() val clazz = loader.loadClass( - "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager") + "org.scalajs.jsenv.phantomjs.JettyWebsocketManager") val ctors = clazz.getConstructors() assert(ctors.length == 1, "JettyWebsocketManager may only have one ctor") diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala similarity index 78% rename from sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala rename to js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala index 02c229ba2..5554ee09a 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala @@ -1,6 +1,6 @@ -package scala.scalajs.sbtplugin.env.phantomjs +package org.scalajs.jsenv.phantomjs -import scala.scalajs.tools.io.IO +import org.scalajs.core.tools.io.IO /** A special [[ClassLoader]] to load the Jetty 8 dependency of [[PhantomJSEnv]] * in a private space. @@ -16,7 +16,7 @@ import scala.scalajs.tools.io.IO * of the Java world. This allows to load another version of Jetty in the same * JVM for the rest of the project. */ -private[sbtplugin] class PhantomJettyClassLoader(jettyLoader: ClassLoader, +final class PhantomJettyClassLoader(jettyLoader: ClassLoader, parent: ClassLoader) extends ClassLoader(parent) { def this(loader: ClassLoader) = @@ -26,11 +26,11 @@ private[sbtplugin] class PhantomJettyClassLoader(jettyLoader: ClassLoader, * Basically everything defined in JettyWebsocketManager. */ private val bridgeClasses = Set( - "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager", - "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$WSLogger", - "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$ComWebSocketListener", - "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$$anon$1", - "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$$anon$2" + "org.scalajs.jsenv.phantomjs.JettyWebsocketManager", + "org.scalajs.jsenv.phantomjs.JettyWebsocketManager$WSLogger", + "org.scalajs.jsenv.phantomjs.JettyWebsocketManager$ComWebSocketListener", + "org.scalajs.jsenv.phantomjs.JettyWebsocketManager$$anon$1", + "org.scalajs.jsenv.phantomjs.JettyWebsocketManager$$anon$2" ) override protected def loadClass(name: String, resolve: Boolean): Class[_] = { diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketListener.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketListener.scala similarity index 79% rename from sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketListener.scala rename to js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketListener.scala index 4faac64c1..b26dc1dcf 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketListener.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketListener.scala @@ -1,4 +1,4 @@ -package scala.scalajs.sbtplugin.env.phantomjs +package org.scalajs.jsenv.phantomjs private[phantomjs] trait WebsocketListener { def onRunning(): Unit diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketManager.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketManager.scala similarity index 80% rename from sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketManager.scala rename to js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketManager.scala index a4668417d..875393c07 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketManager.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketManager.scala @@ -1,4 +1,4 @@ -package scala.scalajs.sbtplugin.env.phantomjs +package org.scalajs.jsenv.phantomjs private[phantomjs] trait WebsocketManager { def start(): Unit diff --git a/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala b/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala similarity index 68% rename from sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala rename to js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala index 23e240d4e..982bd2c92 100644 --- a/sbt-plugin/src/test/scala/scala/scalajs/sbtplugin/test/env/PhantomJSTest.scala +++ b/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala @@ -1,6 +1,6 @@ -package scala.scalajs.sbtplugin.test.env +package org.scalajs.jsenv.test -import scala.scalajs.sbtplugin.env.phantomjs.PhantomJSEnv +import org.scalajs.jsenv.phantomjs.PhantomJSEnv import org.junit.Test diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 9e54bc037..0031a37d1 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -9,7 +9,7 @@ val versionSettings = Seq( val baseSettings = versionSettings ++ Seq( libraryDependencies += - "org.scala-lang.modules.scalajs" %% "scalajs-jasmine-test-framework" % scalaJSVersion % "test" + "org.scala-js" %% "scalajs-jasmine-test-framework" % scalaJSVersion % "test" ) lazy val root = project.in(file(".")). @@ -56,7 +56,7 @@ lazy val testFrameworkJS = project.in(file("testFramework/.js")). settings(testFrameworkSettings: _*). settings( libraryDependencies += - "org.scala-lang.modules.scalajs" %% "scalajs-test-interface" % scalaJSVersion + "org.scala-js" %% "scalajs-test-interface" % scalaJSVersion ) lazy val testFrameworkJVM = project.in(file("testFramework/.jvm")). @@ -65,7 +65,7 @@ lazy val testFrameworkJVM = project.in(file("testFramework/.jvm")). settings( libraryDependencies ++= Seq( "org.scala-sbt" % "test-interface" % "1.0", - "org.scala-lang.modules.scalajs" %% "scalajs-stubs" % scalaJSVersion % "provided" + "org.scala-js" %% "scalajs-stubs" % scalaJSVersion % "provided" ) ) diff --git a/sbt-plugin-test/project/Jetty9Test.scala b/sbt-plugin-test/project/Jetty9Test.scala index 6a84114e2..72b0b71c1 100644 --- a/sbt-plugin-test/project/Jetty9Test.scala +++ b/sbt-plugin-test/project/Jetty9Test.scala @@ -1,12 +1,12 @@ import sbt._ import Keys._ -import scala.scalajs.sbtplugin._ +import org.scalajs.sbtplugin._ import ScalaJSPlugin.autoImport._ import Implicits._ -import scala.scalajs.tools.env._ -import scala.scalajs.tools.io._ +import org.scalajs.jsenv._ +import org.scalajs.core.tools.io._ import org.eclipse.jetty.server._ import org.eclipse.jetty.server.handler._ diff --git a/sbt-plugin-test/project/build.sbt b/sbt-plugin-test/project/build.sbt index 841928979..fd8d93a70 100644 --- a/sbt-plugin-test/project/build.sbt +++ b/sbt-plugin-test/project/build.sbt @@ -1,4 +1,4 @@ -addSbtPlugin("org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % - scala.scalajs.ir.ScalaJSVersions.current) +addSbtPlugin("org.scala-js" % "sbt-scalajs" % + org.scalajs.core.ir.ScalaJSVersions.current) libraryDependencies += "org.eclipse.jetty" % "jetty-server" % "9.2.3.v20140905" From c8c33fbc10c1db5214500dcebf23405c389d9ae8 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 30 Nov 2014 17:30:39 +0100 Subject: [PATCH 30/84] Fix scala-js/scala-js#1315: Add timeout to AsyncJSRunner.await and use it --- sbt-plugin-test/project/Jetty9Test.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sbt-plugin-test/project/Jetty9Test.scala b/sbt-plugin-test/project/Jetty9Test.scala index 72b0b71c1..9d2a3d33b 100644 --- a/sbt-plugin-test/project/Jetty9Test.scala +++ b/sbt-plugin-test/project/Jetty9Test.scala @@ -14,6 +14,8 @@ import org.eclipse.jetty.util.component._ import java.io.File +import scala.concurrent.duration._ + object Jetty9Test { private val jettyPort = 23548 @@ -63,7 +65,7 @@ object Jetty9Test { }) jetty.start() - runner.await() + runner.await(30.seconds) jetty.join() } From 7dff77b0f5ce2e51e2798d96fcb73580f80ad83f Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 30 Nov 2014 17:35:36 +0100 Subject: [PATCH 31/84] Fix scala-js/scala-js#1330: Remove sleep-wait in PhantomJSEnv --- .../scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index c547c485a..5f05fa272 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -24,6 +24,8 @@ import scala.io.Source import scala.collection.mutable import scala.annotation.tailrec +import scala.concurrent.ExecutionContext + class PhantomJSEnv( phantomjsPath: String = "phantomjs", addArgs: Seq[String] = Seq.empty, @@ -89,6 +91,8 @@ class PhantomJSEnv( def onOpen(): Unit = synchronized(notifyAll()) def onClose(): Unit = synchronized(notifyAll()) + future.onComplete(_ => synchronized(notifyAll()))(ExecutionContext.global) + def onMessage(msg: String): Unit = synchronized { recvBuf.enqueue(msg) notifyAll() @@ -262,7 +266,7 @@ class PhantomJSEnv( */ private def awaitConnection(): Boolean = { while (!mgr.isConnected && !mgr.isClosed && isRunning) - wait(200) // We sleep-wait for isRunning + wait() mgr.isConnected } From 16ddfb6c02e67df75d2c5a4af9befc97f3c6405e Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Mon, 1 Dec 2014 11:49:28 +0100 Subject: [PATCH 32/84] Fix scala-js/scala-js#1331: Repair %%% the macro (using a hack) --- sbt-plugin-test/build.sbt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 0031a37d1..9bb7e9001 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -95,3 +95,8 @@ lazy val multiTestJVM = project.in(file("multiTest/jvm")). libraryDependencies += "com.novocode" % "junit-interface" % "0.9" % "test" ). dependsOn(testFrameworkJVM % "test") + +// Test %%% macro - #1331 +val unusedSettings = Seq( + libraryDependencies += "org.example" %%% "dummy" % "0.1" +) From bcec88d67278cec65e12770f8f37d78dcf64ffe3 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Mon, 1 Dec 2014 17:15:12 +0100 Subject: [PATCH 33/84] Fix scala-js/scala-js#1343: Add a RetryingComJSEnv and use it for CI --- .../org/scalajs/jsenv/RetryingComJSEnv.scala | 189 ++++++++++++++++++ .../jsenv/test/RetryingComJSEnvTest.scala | 99 +++++++++ 2 files changed, 288 insertions(+) create mode 100644 js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala create mode 100644 js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala new file mode 100644 index 000000000..f104d2b90 --- /dev/null +++ b/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala @@ -0,0 +1,189 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package org.scalajs.jsenv + +import org.scalajs.core.tools.io._ +import org.scalajs.core.tools.classpath._ +import org.scalajs.core.tools.logging._ + +import scala.concurrent.{Future, Promise, ExecutionContext} +import scala.collection.mutable +import scala.annotation.tailrec +import scala.util.control.NonFatal +import scala.util.{Try, Failure, Success} + +/** A RetryingComJSEnv allows to automatically retry if a call to the underlying + * ComJSRunner fails. + * + * While it protects the JVM side from observing state that differs inbetween + * runs that have been retried, it assumes that the executed JavaScript code + * does not have side-effects other than the ones visible through the channel + * (e.g. writing to a file). It is the users responsibility to ensure this + * property. + * + * No retrying is performed for synchronous, or normal asynchronous runs. + */ +final class RetryingComJSEnv(baseEnv: ComJSEnv, + maxRetries: Int) extends ComJSEnv { + + def this(baseEnv: ComJSEnv) = this(baseEnv, 5) + + def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): JSRunner = { + baseEnv.jsRunner(classpath, code, logger, console) + } + + def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): AsyncJSRunner = { + baseEnv.asyncRunner(classpath, code, logger, console) + } + + def comRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): ComJSRunner = { + new RetryingComJSRunner(classpath, code, logger, console) + } + + /** Hack to work around abstract override in ComJSRunner */ + private trait DummyJSRunner { + def stop(): Unit = () + } + + private class RetryingComJSRunner(classpath: CompleteClasspath, + code: VirtualJSFile, logger: Logger, + console: JSConsole) extends DummyJSRunner with ComJSRunner { + + private[this] val promise = Promise[Unit] + + private[this] var curRunner = + baseEnv.comRunner(classpath, code, logger, console) + + private[this] var hasReceived = false + private[this] var retryCount = 0 + + private[this] val log = mutable.Buffer.empty[LogItem] + + def future: Future[Unit] = promise.future + + def start(): Future[Unit] = { + require(log.isEmpty, "start() may only be called once") + logAndDo(Start) + future + } + + override def stop(): Unit = { + require(log.nonEmpty, "start() must have been called") + close() + logAndDo(Stop) + } + + def send(msg: String): Unit = { + require(log.nonEmpty, "start() must have been called") + logAndDo(Send(msg)) + } + + def receive(): String = { + @tailrec + def recLoop(): String = { + // Need to use Try for tailrec + Try { + val result = curRunner.receive() + // At this point, we are sending state to the JVM, we cannot retry + // after this. + hasReceived = true + result + } match { + case Failure(t) => + retry(t) + recLoop() + case Success(v) => v + } + } + + recLoop() + } + + def close(): Unit = { + require(log.nonEmpty, "start() must have been called") + logAndDo(Close) + } + + @tailrec + private final def retry(cause: Throwable): Unit = { + retryCount += 1 + + // Accesses to promise and swaps in the curRunner must be synchronized + synchronized { + if (hasReceived || retryCount > maxRetries || promise.isCompleted) + throw cause + + logger.warn("Retrying to launch a " + baseEnv.getClass.getName + + " after " + cause.toString) + + val oldRunner = curRunner + + curRunner = try { + baseEnv.comRunner(classpath, code, logger, console) + } catch { + case NonFatal(t) => + logger.error("Could not retry: creating an new runner failed: " + + t.toString) + throw cause + } + + try oldRunner.stop() // just in case + catch { + case NonFatal(t) => // ignore + } + } + + // Replay the whole log + // Need to use Try for tailrec + Try(log.foreach(executeTask)) match { + case Failure(t) => retry(t) + case _ => + } + } + + private def logAndDo(task: LogItem) = { + log += task + try executeTask(task) + catch { + case NonFatal(t) => retry(t) + } + } + + private def executeTask(task: LogItem) = task match { + case Start => + import ExecutionContext.Implicits.global + val runner = curRunner + runner.start() onComplete { result => + // access to curRunner and promise must be synchronized + synchronized { + if (curRunner eq runner) + promise.complete(result) + } + } + case Send(msg) => + curRunner.send(msg) + case Stop => + curRunner.stop() + case Close => + curRunner.close() + } + + private sealed trait LogItem + private case object Start extends LogItem + private case class Send(msg: String) extends LogItem + private case object Stop extends LogItem + private case object Close extends LogItem + + } + +} diff --git a/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala b/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala new file mode 100644 index 000000000..7ad46d58b --- /dev/null +++ b/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala @@ -0,0 +1,99 @@ +package org.scalajs.jsenv.test + +import org.scalajs.jsenv.rhino.RhinoJSEnv +import org.scalajs.jsenv._ + +import org.scalajs.core.tools.io._ +import org.scalajs.core.tools.classpath._ +import org.scalajs.core.tools.logging._ +import org.scalajs.core.tools.sem._ + +import scala.concurrent.Future + +import org.junit.Test + +class RetryingComJSEnvTest extends JSEnvTest with ComTests { + + private final val maxFails = 5 + + override protected def logger: Logger = NullLogger + + protected def newJSEnv = { + val baseEnv = new RhinoJSEnv(Semantics.Defaults) + new RetryingComJSEnv(new FailingEnv(baseEnv), maxFails) + } + + private final class FailingEnv(baseEnv: ComJSEnv) extends ComJSEnv { + private[this] var fails = 0 + private[this] var failedReceive = false + + def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): JSRunner = { + baseEnv.jsRunner(classpath, code, logger, console) + } + + def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): AsyncJSRunner = { + baseEnv.asyncRunner(classpath, code, logger, console) + } + + def comRunner(classpath: CompleteClasspath, code: VirtualJSFile, + logger: Logger, console: JSConsole): ComJSRunner = { + new FailingComJSRunner( + baseEnv.comRunner(classpath, code, logger, console)) + } + + /** Hack to work around abstract override in ComJSRunner */ + private trait DummyJSRunner { + def stop(): Unit = () + } + + private class FailingComJSRunner(baseRunner: ComJSRunner) + extends DummyJSRunner with ComJSRunner { + + def future = baseRunner.future + + def send(msg: String): Unit = { + maybeFail() + baseRunner.send(msg) + } + + def receive(): String = { + if (shouldFail) { + failedReceive = true + fail() + } + baseRunner.receive() + } + + + def start(): Future[Unit] = { + maybeFail() + baseRunner.start() + } + + override def stop(): Unit = { + maybeFail() + baseRunner.stop() + } + + def close(): Unit = { + maybeFail() + baseRunner.close() + } + + private def shouldFail = !failedReceive && fails < maxFails + + private def maybeFail() = { + if (shouldFail) + fail() + } + + private def fail() = { + fails += 1 + sys.error("Dummy fail for testing purposes") + } + } + } + +} From 9a9c9efdf9244224d81f820114bc95b346e90535 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Mon, 1 Dec 2014 12:14:38 +0100 Subject: [PATCH 34/84] Fix scala-js/scala-js#1067: Add a simple CrossProject mechanism --- sbt-plugin-test/build.sbt | 63 ++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 9bb7e9001..bd147fbbd 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -44,57 +44,44 @@ lazy val jetty9 = project.settings(baseSettings: _*). Jetty9Test.runSetting ) -val testFrameworkSettings = Seq( - name := "Dummy cross JS/JVM test framework", - unmanagedSourceDirectories in Compile += - baseDirectory.value / ".." / "src" / "main" / "scala" -) - -lazy val testFrameworkJS = project.in(file("testFramework/.js")). - enablePlugins(ScalaJSPlugin). +lazy val testFramework = crossProject.crossType(CrossType.Pure). settings(versionSettings: _*). - settings(testFrameworkSettings: _*). - settings( + settings(name := "Dummy cross JS/JVM test framework"). + jsSettings( libraryDependencies += "org.scala-js" %% "scalajs-test-interface" % scalaJSVersion - ) - -lazy val testFrameworkJVM = project.in(file("testFramework/.jvm")). - settings(versionSettings: _*). - settings(testFrameworkSettings: _*). - settings( + ). + jvmSettings( libraryDependencies ++= Seq( "org.scala-sbt" % "test-interface" % "1.0", "org.scala-js" %% "scalajs-stubs" % scalaJSVersion % "provided" ) ) -val multiTestSettings = Seq( - testFrameworks ++= Seq( - TestFramework("sbttest.framework.DummyFramework"), - TestFramework("inexistent.Foo", "another.strange.Bar") - ), - unmanagedSourceDirectories in Compile += - baseDirectory.value / ".." / "shared" / "src" / "main" / "scala", - unmanagedSourceDirectories in Test += - baseDirectory.value / ".." / "shared" / "src" / "test" / "scala" -) - -lazy val multiTestJS = project.in(file("multiTest/js")). - enablePlugins(ScalaJSPlugin). - settings(baseSettings: _*). - settings(multiTestSettings: _*). - settings(name := "Multi test framework test JS"). - dependsOn(testFrameworkJS % "test") +lazy val testFrameworkJS = testFramework.js +lazy val testFrameworkJVM = testFramework.jvm -lazy val multiTestJVM = project.in(file("multiTest/jvm")). - settings(versionSettings: _*). - settings(multiTestSettings: _*). +lazy val multiTest = crossProject. settings( + testFrameworks ++= Seq( + TestFramework("sbttest.framework.DummyFramework"), + TestFramework("inexistent.Foo", "another.strange.Bar") + ) + ). + jsSettings(baseSettings: _*). + jsSettings( + name := "Multi test framework test JS" + ). + jvmSettings(versionSettings: _*). + jvmSettings( name := "Multi test framework test JVM", - libraryDependencies += "com.novocode" % "junit-interface" % "0.9" % "test" + libraryDependencies += + "com.novocode" % "junit-interface" % "0.9" % "test" ). - dependsOn(testFrameworkJVM % "test") + dependsOn(testFramework % "test") + +lazy val multiTestJS = multiTest.js +lazy val multiTestJVM = multiTest.jvm // Test %%% macro - #1331 val unusedSettings = Seq( From d2a8aac538f1b2deed6994ffbdd2fd3796677913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 24 Dec 2014 13:48:58 +0100 Subject: [PATCH 35/84] Fix scala-js/scala-js#1408: packageJSDependencies is broken. --- sbt-plugin-test/build.sbt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index bd147fbbd..1acdcfc89 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -39,6 +39,8 @@ lazy val jetty9 = project.settings(baseSettings: _*). RuntimeDOM, "org.webjars" % "jquery" % "1.10.2" / "jquery.js" ), + // A test for packageJSDependencies, although we don't use it + skip in packageJSDependencies := false, // Use PhantomJS, allow cross domain requests postLinkJSEnv := PhantomJSEnv(args = Seq("--web-security=no")).value, Jetty9Test.runSetting From cc94b92d1debbca9f137b587873ba7464df95a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 8 Jan 2015 14:39:32 +0100 Subject: [PATCH 36/84] Upgrade to Scala 2.11.5. --- sbt-plugin-test/build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 1acdcfc89..7ac6cccbe 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -4,7 +4,7 @@ version := scalaJSVersion val versionSettings = Seq( version := scalaJSVersion, - scalaVersion := "2.11.2" + scalaVersion := "2.11.5" ) val baseSettings = versionSettings ++ Seq( From 61d236f42e23d5a87e8aa2c5e16ac609dd25d574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 15 Jan 2015 11:03:41 +0100 Subject: [PATCH 37/84] Fix scala-js/scala-js#1458: Use file:/// URIs for files in PhantomJS launchers. Both the HTML page and the .js launcher. --- .../scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index 5f05fa272..96d7d8c95 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -11,7 +11,7 @@ package org.scalajs.jsenv.phantomjs import org.scalajs.jsenv._ -import org.scalajs.core.ir.Utils.escapeJS +import org.scalajs.core.ir.Utils.{escapeJS, fixFileURI} import org.scalajs.core.tools.io._ import org.scalajs.core.tools.classpath._ @@ -285,7 +285,7 @@ class PhantomJSEnv( override protected def writeJSFile(file: VirtualJSFile, writer: Writer) = { file match { case file: FileVirtualJSFile => - val fname = htmlEscape(file.file.getAbsolutePath) + val fname = htmlEscape(fixFileURI(file.file.toURI).toASCIIString) writer.write( s"""""" + "\n") case _ => @@ -376,7 +376,7 @@ class PhantomJSEnv( out.write( s"""// Scala.js Phantom.js launcher |var page = require('webpage').create(); - |var url = "${escapeJS(webF.getAbsolutePath)}"; + |var url = "${escapeJS(fixFileURI(webF.toURI).toASCIIString)}"; |var autoExit = $autoExit; |page.onConsoleMessage = function(msg) { | console.log(msg); From 7fb802c3dd178c81d2b964f6679df36957bc3aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 17 Jan 2015 13:20:09 +0100 Subject: [PATCH 38/84] Fix one potential race condition in startup of PhantomComRunner. --- .../jsenv/phantomjs/PhantomJSEnv.scala | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index 96d7d8c95..4597f2de3 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -87,7 +87,13 @@ class PhantomJSEnv( val mgr: WebsocketManager = loadMgr() - def onRunning(): Unit = synchronized(notifyAll()) + protected var mgrIsRunning: Boolean = false + + def onRunning(): Unit = synchronized { + mgrIsRunning = true + notifyAll() + } + def onOpen(): Unit = synchronized(notifyAll()) def onClose(): Unit = synchronized(notifyAll()) @@ -104,16 +110,6 @@ class PhantomJSEnv( mgr.start() - /** The websocket server starts asynchronously, but we need the port it is - * running on. This method waits until the port is non-negative and - * returns its value. - */ - private def waitForPort(): Int = { - while (mgr.localPort < 0) - wait() - mgr.localPort - } - private def comSetup = { def maybeExit(code: Int) = if (autoExit) @@ -121,7 +117,18 @@ class PhantomJSEnv( else "" - val serverPort = waitForPort() + /* The WebSocket server starts asynchronously. We must wait for it to + * be fully operational before a) retrieving the port it is running on + * and b) feeding the connecting JS script to the VM. + */ + synchronized { + while (!mgrIsRunning) + wait() + } + + val serverPort = mgr.localPort + assert(serverPort > 0, + s"Manager running with a non-positive port number: $serverPort") val code = s""" |(function() { From dea0e555b9729b3b308f79ef787675f94e01a0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 18 Jan 2015 14:02:36 +0100 Subject: [PATCH 39/84] Add more diagnostics in PhantomJSEnv. --- .../org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index 4597f2de3..2595314da 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -24,7 +24,7 @@ import scala.io.Source import scala.collection.mutable import scala.annotation.tailrec -import scala.concurrent.ExecutionContext +import scala.concurrent.{ExecutionContext, TimeoutException} class PhantomJSEnv( phantomjsPath: String = "phantomjs", @@ -123,7 +123,10 @@ class PhantomJSEnv( */ synchronized { while (!mgrIsRunning) - wait() + wait(10000) + if (!mgrIsRunning) + throw new TimeoutException( + "The PhantomJS WebSocket server startup timed out") } val serverPort = mgr.localPort @@ -183,6 +186,8 @@ class PhantomJSEnv( | }; | websocket.onclose = function(evt) { | websocket = null; + | if (outMsgBuf !== null) + | throw new Error("WebSocket closed before being opened: " + evt); | ${maybeExit(0)} | }; | websocket.onmessage = recvImpl(recvCB); @@ -273,7 +278,10 @@ class PhantomJSEnv( */ private def awaitConnection(): Boolean = { while (!mgr.isConnected && !mgr.isClosed && isRunning) - wait() + wait(10000) + if (!mgr.isConnected && !mgr.isClosed && isRunning) + throw new TimeoutException( + "The PhantomJS WebSocket client took too long to connect") mgr.isConnected } From 88e447f824dffd1c52b1b2b4b0e4a06c6f05698f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 20 Jan 2015 06:51:32 +0100 Subject: [PATCH 40/84] Add support for timeouts in ComJSRunner.receive(). --- .../org/scalajs/jsenv/RetryingComJSEnv.scala | 9 +-- .../jsenv/phantomjs/PhantomJSEnv.scala | 57 +++++++++++++------ .../jsenv/test/RetryingComJSEnvTest.scala | 9 +-- 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala index f104d2b90..140332208 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala @@ -14,6 +14,7 @@ import org.scalajs.core.tools.classpath._ import org.scalajs.core.tools.logging._ import scala.concurrent.{Future, Promise, ExecutionContext} +import scala.concurrent.duration.Duration import scala.collection.mutable import scala.annotation.tailrec import scala.util.control.NonFatal @@ -21,7 +22,7 @@ import scala.util.{Try, Failure, Success} /** A RetryingComJSEnv allows to automatically retry if a call to the underlying * ComJSRunner fails. - * + * * While it protects the JVM side from observing state that differs inbetween * runs that have been retried, it assumes that the executed JavaScript code * does not have side-effects other than the ones visible through the channel @@ -57,7 +58,7 @@ final class RetryingComJSEnv(baseEnv: ComJSEnv, private class RetryingComJSRunner(classpath: CompleteClasspath, code: VirtualJSFile, logger: Logger, - console: JSConsole) extends DummyJSRunner with ComJSRunner { + console: JSConsole) extends DummyJSRunner with ComJSRunner { private[this] val promise = Promise[Unit] @@ -88,12 +89,12 @@ final class RetryingComJSEnv(baseEnv: ComJSEnv, logAndDo(Send(msg)) } - def receive(): String = { + def receive(timeout: Duration): String = { @tailrec def recLoop(): String = { // Need to use Try for tailrec Try { - val result = curRunner.receive() + val result = curRunner.receive(timeout) // At this point, we are sending state to the JVM, we cannot retry // after this. hasReceived = true diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index 2595314da..786b9d86a 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -10,6 +10,7 @@ package org.scalajs.jsenv.phantomjs import org.scalajs.jsenv._ +import org.scalajs.jsenv.Utils.OptDeadline import org.scalajs.core.ir.Utils.{escapeJS, fixFileURI} @@ -25,6 +26,7 @@ import scala.collection.mutable import scala.annotation.tailrec import scala.concurrent.{ExecutionContext, TimeoutException} +import scala.concurrent.duration.Duration class PhantomJSEnv( phantomjsPath: String = "phantomjs", @@ -107,6 +109,7 @@ class PhantomJSEnv( def log(msg: String): Unit = logger.debug(s"PhantomJS WS Jetty: $msg") private[this] val recvBuf = mutable.Queue.empty[String] + private[this] val fragmentsBuf = new StringBuilder mgr.start() @@ -241,34 +244,52 @@ class PhantomJSEnv( } } - def receive(): String = synchronized { + def receive(timeout: Duration): String = synchronized { if (recvBuf.isEmpty && !awaitConnection()) throw new ComJSEnv.ComClosedException("Phantom.js isn't connected") + val deadline = OptDeadline(timeout) + @tailrec - def loop(acc: String): String = { - val frag = receiveFrag() - val newAcc = acc + frag.substring(1) - - if (frag(0) == '0') - newAcc - else if (frag(0) == '1') - loop(newAcc) - else + def loop(): String = { + /* The fragments are accumulated in an instance-wide buffer in case + * receiving a non-first fragment times out. + */ + val frag = receiveFrag(deadline) + fragmentsBuf ++= frag.substring(1) + + if (frag(0) == '0') { + val result = fragmentsBuf.result() + fragmentsBuf.clear() + result + } else if (frag(0) == '1') { + loop() + } else { throw new AssertionError("Bad fragmentation flag in " + frag) + } } - loop("") + try { + loop() + } catch { + case e: Throwable if !e.isInstanceOf[TimeoutException] => + fragmentsBuf.clear() // the protocol is broken, so discard the buffer + throw e + } } - private def receiveFrag(): String = { - while (recvBuf.isEmpty && !mgr.isClosed) - wait() + private def receiveFrag(deadline: OptDeadline): String = { + while (recvBuf.isEmpty && !mgr.isClosed && !deadline.isOverdue) + wait(deadline.millisLeft) + + if (recvBuf.isEmpty) { + if (mgr.isClosed) + throw new ComJSEnv.ComClosedException + else + throw new TimeoutException("Timeout expired") + } - if (recvBuf.isEmpty) - throw new ComJSEnv.ComClosedException - else - recvBuf.dequeue() + recvBuf.dequeue() } def close(): Unit = mgr.stop() diff --git a/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala b/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala index 7ad46d58b..f7fd66d88 100644 --- a/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala +++ b/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala @@ -9,6 +9,7 @@ import org.scalajs.core.tools.logging._ import org.scalajs.core.tools.sem._ import scala.concurrent.Future +import scala.concurrent.duration.Duration import org.junit.Test @@ -50,7 +51,7 @@ class RetryingComJSEnvTest extends JSEnvTest with ComTests { private class FailingComJSRunner(baseRunner: ComJSRunner) extends DummyJSRunner with ComJSRunner { - + def future = baseRunner.future def send(msg: String): Unit = { @@ -58,12 +59,12 @@ class RetryingComJSEnvTest extends JSEnvTest with ComTests { baseRunner.send(msg) } - def receive(): String = { + def receive(timeout: Duration): String = { if (shouldFail) { failedReceive = true fail() } - baseRunner.receive() + baseRunner.receive(timeout) } @@ -85,7 +86,7 @@ class RetryingComJSEnvTest extends JSEnvTest with ComTests { private def shouldFail = !failedReceive && fails < maxFails private def maybeFail() = { - if (shouldFail) + if (shouldFail) fail() } From dc5f2790e518510f5fb7a187153ec9cda7a592ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 21 Jan 2015 22:38:39 +0100 Subject: [PATCH 41/84] Strengthen the guarantees of AsyncJSRunner.stop(). It is now guaranteed that stop() can be called, and is a no-op, when the runner has already terminated, or when stop() has already been called. --- .../test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala b/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala index f7fd66d88..959f17928 100644 --- a/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala +++ b/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala @@ -67,7 +67,6 @@ class RetryingComJSEnvTest extends JSEnvTest with ComTests { baseRunner.receive(timeout) } - def start(): Future[Unit] = { maybeFail() baseRunner.start() From 81ecbb3040461d80b9726d5093d0f737d6f34c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 26 Feb 2015 16:40:37 +0100 Subject: [PATCH 42/84] Fix scala-js/scala-js#1496: Allow partial paths in jsDependencies. Partial paths written in jsDependencies (including just file names) are shorthands for the full relative paths wrt. the classpath entries. Different partial paths resolving to the same full relative paths are considered equivalent for dependency resolution and ordering. If a partial path does not resolve to a unique full relative path on the classpath, a JSLibResolveException is thrown. --- sbt-plugin-test/build.sbt | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 7ac6cccbe..61d846345 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -85,6 +85,56 @@ lazy val multiTest = crossProject. lazy val multiTestJS = multiTest.js lazy val multiTestJVM = multiTest.jvm +lazy val jsDependenciesTest = project.settings(versionSettings: _*). + enablePlugins(ScalaJSPlugin). + settings( + jsDependencies ++= Seq( + "org.webjars" % "historyjs" % "1.8.0" / "uncompressed/history.js", + ProvidedJS / "some-jquery-plugin.js" dependsOn "1.10.2/jquery.js", + ProvidedJS / "js/foo.js" dependsOn "uncompressed/history.js", + + // cause a circular dependency error if both "history.js"'s are considered equal + "org.webjars" % "historyjs" % "1.8.0" / "compressed/history.js" dependsOn "foo.js", + + // cause a duplicate commonJSName if the following are not considered equal + "org.webjars" % "mustachejs" % "0.8.2" / "mustache.js" commonJSName "Mustache", + "org.webjars" % "mustachejs" % "0.8.2" / "0.8.2/mustache.js" commonJSName "Mustache" + ) + ). + settings(inConfig(Compile)(Seq( + skip in packageJSDependencies := false, + packageJSDependencies <<= packageJSDependencies.dependsOn(Def.task { + // perform verifications on the ordering and deduplications + val cp = scalaJSPreLinkClasspath.value + val relPaths = cp.jsLibs.map(_.info.relPath) + + assert(relPaths.toSet == Set( + "META-INF/resources/webjars/mustachejs/0.8.2/mustache.js", + "META-INF/resources/webjars/historyjs/1.8.0/scripts/uncompressed/history.js", + "META-INF/resources/webjars/historyjs/1.8.0/scripts/compressed/history.js", + "META-INF/resources/webjars/jquery/1.10.2/jquery.js", + "js/foo.js", + "js/some-jquery-plugin.js"), + s"Bad set of relPathes: ${relPaths.toSet}") + + val jQueryIndex = relPaths.indexWhere(_ endsWith "/jquery.js") + val jQueryPluginIndex = relPaths.indexWhere(_ endsWith "/some-jquery-plugin.js") + assert(jQueryPluginIndex > jQueryIndex, + "the jQuery plugin appears before jQuery") + + val uncompressedHistoryIndex = relPaths.indexWhere(_ endsWith "/uncompressed/history.js") + val fooIndex = relPaths.indexWhere(_ endsWith "/foo.js") + val compressedHistoryIndex = relPaths.indexWhere(_ endsWith "/compressed/history.js") + assert(fooIndex > uncompressedHistoryIndex, + "foo.js appears before uncompressed/history.js") + assert(compressedHistoryIndex > fooIndex, + "compressed/history.js appears before foo.js") + + streams.value.log.info("jsDependencies resolution test passed") + }) + )): _*). + dependsOn(jetty9) // depends on jQuery + // Test %%% macro - #1331 val unusedSettings = Seq( libraryDependencies += "org.example" %%% "dummy" % "0.1" From e03694f1ae9c2b2f99b977b1acfad68ad58f34a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 5 Mar 2015 15:23:53 +0100 Subject: [PATCH 43/84] Upgrade to Scala 2.10.5 and 2.11.6. --- sbt-plugin-test/build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 61d846345..139d18680 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -4,7 +4,7 @@ version := scalaJSVersion val versionSettings = Seq( version := scalaJSVersion, - scalaVersion := "2.11.5" + scalaVersion := "2.11.6" ) val baseSettings = versionSettings ++ Seq( From dfc27e068327f1043510b8ea65087c04ff3cbff9 Mon Sep 17 00:00:00 2001 From: Aleksey Fomkin Date: Thu, 26 Mar 2015 23:58:28 +0300 Subject: [PATCH 44/84] Fix scala-js/scala-js#1571: Make FrameworkDetector resilient to other output --- sbt-plugin-test/build.sbt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 139d18680..bfd04ab91 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -72,7 +72,9 @@ lazy val multiTest = crossProject. ). jsSettings(baseSettings: _*). jsSettings( - name := "Multi test framework test JS" + name := "Multi test framework test JS", + // Make FrameworkDetector resilient to other output - #1572 + jsDependencies in Test += ProvidedJS / "consoleWriter.js" ). jvmSettings(versionSettings: _*). jvmSettings( From f16ad8d635b470dcfbaea08da27065b9c9f7afcf Mon Sep 17 00:00:00 2001 From: Aleksey Fomkin Date: Thu, 26 Mar 2015 13:39:42 +0300 Subject: [PATCH 45/84] Fix scala-js/scala-js#1569: Allow crossProject's JVM and JS parts to be used in a ProjectRef. --- sbt-plugin-test/build.sbt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 139d18680..68edf50d0 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -12,8 +12,11 @@ val baseSettings = versionSettings ++ Seq( "org.scala-js" %% "scalajs-jasmine-test-framework" % scalaJSVersion % "test" ) +lazy val referencedCrossProjectJS = ProjectRef(file("referencedCrossProject"), "referencedCrossProjectJS") +lazy val referencedCrossProjectJVM = ProjectRef(file("referencedCrossProject"), "referencedCrossProjectJVM") + lazy val root = project.in(file(".")). - aggregate(noDOM, withDOM, multiTestJS, multiTestJVM) + aggregate(noDOM, withDOM, multiTestJS, multiTestJVM, referencedCrossProjectJS, referencedCrossProjectJVM) lazy val noDOM = project.settings(baseSettings: _*). enablePlugins(ScalaJSPlugin). From 1ceb6b7bef1208b93baf76661196cc76365aa382 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Wed, 15 Apr 2015 20:19:35 -0700 Subject: [PATCH 46/84] Fix scala-js/scala-js#1564: Allow filtering of JS dependency manifests This allows to resolve relative path conflicts in manifests inherited from dependencies as the test shows. The typical example (for which we provide a convenience filter) is a dependency that does not declare specific enough relative paths. Binary compatibility of PartialClasspath.resolve is a bit funky here: The default parameter for the one argument version is now provided by the parameter of the two argument version. However, since the values equal, this is fine. Also, argument names need not be deprecated, since the overloading resolution works properly, if filter is used. --- sbt-plugin-test/build.sbt | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 139d18680..757468c06 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -1,3 +1,5 @@ +import org.scalajs.core.tools.jsdep.ManifestFilters + name := "Scala.js sbt test" version := scalaJSVersion @@ -37,6 +39,8 @@ lazy val jetty9 = project.settings(baseSettings: _*). name := "Scala.js sbt test with jetty9 on classpath", jsDependencies ++= Seq( RuntimeDOM, + // The jsDependenciesTest relies on this jQuery dependency + // If you change it, make sure we still test properly "org.webjars" % "jquery" % "1.10.2" / "jquery.js" ), // A test for packageJSDependencies, although we don't use it @@ -98,8 +102,13 @@ lazy val jsDependenciesTest = project.settings(versionSettings: _*). // cause a duplicate commonJSName if the following are not considered equal "org.webjars" % "mustachejs" % "0.8.2" / "mustache.js" commonJSName "Mustache", - "org.webjars" % "mustachejs" % "0.8.2" / "0.8.2/mustache.js" commonJSName "Mustache" - ) + "org.webjars" % "mustachejs" % "0.8.2" / "0.8.2/mustache.js" commonJSName "Mustache", + + // cause an ambiguity with jQuery dependency from jetty9 project (if we don't filter) + ProvidedJS / "js/customJQuery/jquery.js" dependsOn "1.10.2/jquery.js" + ), + jsManifestFilter := ManifestFilters.reinterpretResourceNames("jetty9")( + "jquery.js" -> "1.10.2/jquery.js") ). settings(inConfig(Compile)(Seq( skip in packageJSDependencies := false, @@ -114,10 +123,11 @@ lazy val jsDependenciesTest = project.settings(versionSettings: _*). "META-INF/resources/webjars/historyjs/1.8.0/scripts/compressed/history.js", "META-INF/resources/webjars/jquery/1.10.2/jquery.js", "js/foo.js", - "js/some-jquery-plugin.js"), + "js/some-jquery-plugin.js", + "js/customJQuery/jquery.js"), s"Bad set of relPathes: ${relPaths.toSet}") - val jQueryIndex = relPaths.indexWhere(_ endsWith "/jquery.js") + val jQueryIndex = relPaths.indexWhere(_ endsWith "1.10.2/jquery.js") val jQueryPluginIndex = relPaths.indexWhere(_ endsWith "/some-jquery-plugin.js") assert(jQueryPluginIndex > jQueryIndex, "the jQuery plugin appears before jQuery") From d1d8d3982159c4e9b992bb9f5022701b8146ff85 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 28 Apr 2015 21:42:40 -0700 Subject: [PATCH 47/84] Fix scala-js/scala-js#1621: Don't skip packaging JS dependencies by default --- sbt-plugin-test/build.sbt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index cb23c82ef..49ced0967 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -40,14 +40,13 @@ lazy val jetty9 = project.settings(baseSettings: _*). enablePlugins(ScalaJSPlugin). settings( name := "Scala.js sbt test with jetty9 on classpath", + // This project also tests packageJSDependencies, although we don't use it jsDependencies ++= Seq( RuntimeDOM, // The jsDependenciesTest relies on this jQuery dependency // If you change it, make sure we still test properly "org.webjars" % "jquery" % "1.10.2" / "jquery.js" ), - // A test for packageJSDependencies, although we don't use it - skip in packageJSDependencies := false, // Use PhantomJS, allow cross domain requests postLinkJSEnv := PhantomJSEnv(args = Seq("--web-security=no")).value, Jetty9Test.runSetting @@ -116,7 +115,6 @@ lazy val jsDependenciesTest = project.settings(versionSettings: _*). "jquery.js" -> "1.10.2/jquery.js") ). settings(inConfig(Compile)(Seq( - skip in packageJSDependencies := false, packageJSDependencies <<= packageJSDependencies.dependsOn(Def.task { // perform verifications on the ordering and deduplications val cp = scalaJSPreLinkClasspath.value From af71a21fd05bdd09b417bc3ffd9153611c325101 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Thu, 23 Apr 2015 01:01:59 -0700 Subject: [PATCH 48/84] Fix scala-js/scala-js#1592: Support for minified JS dependencies --- sbt-plugin-test/build.sbt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 49ced0967..9804a89f8 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -109,7 +109,10 @@ lazy val jsDependenciesTest = project.settings(versionSettings: _*). "org.webjars" % "mustachejs" % "0.8.2" / "0.8.2/mustache.js" commonJSName "Mustache", // cause an ambiguity with jQuery dependency from jetty9 project (if we don't filter) - ProvidedJS / "js/customJQuery/jquery.js" dependsOn "1.10.2/jquery.js" + ProvidedJS / "js/customJQuery/jquery.js" dependsOn "1.10.2/jquery.js", + + // Test minified dependencies + "org.webjars" % "immutable" % "3.4.0" / "immutable.js" minified "immutable.min.js" ), jsManifestFilter := ManifestFilters.reinterpretResourceNames("jetty9")( "jquery.js" -> "1.10.2/jquery.js") @@ -125,11 +128,18 @@ lazy val jsDependenciesTest = project.settings(versionSettings: _*). "META-INF/resources/webjars/historyjs/1.8.0/scripts/uncompressed/history.js", "META-INF/resources/webjars/historyjs/1.8.0/scripts/compressed/history.js", "META-INF/resources/webjars/jquery/1.10.2/jquery.js", + "META-INF/resources/webjars/immutable/3.4.0/immutable.js", "js/foo.js", "js/some-jquery-plugin.js", "js/customJQuery/jquery.js"), s"Bad set of relPathes: ${relPaths.toSet}") + val minifiedRelPaths = cp.jsLibs.flatMap(_.info.relPathMinified) + + assert(minifiedRelPaths.toSet == Set( + "META-INF/resources/webjars/immutable/3.4.0/immutable.min.js"), + s"Bad set of minifiedRelPathes: ${minifiedRelPaths.toSet}") + val jQueryIndex = relPaths.indexWhere(_ endsWith "1.10.2/jquery.js") val jQueryPluginIndex = relPaths.indexWhere(_ endsWith "/some-jquery-plugin.js") assert(jQueryPluginIndex > jQueryIndex, From b1f6b151a24279ebed0356a64be51554c801ff7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 5 May 2015 22:33:36 +0200 Subject: [PATCH 49/84] Fix the CI matrix to actually run the testSuite with PhantomJS. -_-' --- .../src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala index 140332208..3d33b1026 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala @@ -31,8 +31,8 @@ import scala.util.{Try, Failure, Success} * * No retrying is performed for synchronous, or normal asynchronous runs. */ -final class RetryingComJSEnv(baseEnv: ComJSEnv, - maxRetries: Int) extends ComJSEnv { +final class RetryingComJSEnv(val baseEnv: ComJSEnv, + val maxRetries: Int) extends ComJSEnv { def this(baseEnv: ComJSEnv) = this(baseEnv, 5) From 8ca428795a3c1d5f3f3702a3360b466c5a7adb82 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Thu, 14 May 2015 16:23:15 -0700 Subject: [PATCH 50/84] Fix scala-js/scala-js#1594: Ensure PhantomJSEnv throws upon syntax error --- .../scala/org/scalajs/jsenv/test/PhantomJSTest.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala b/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala index 982bd2c92..a9a957348 100644 --- a/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala +++ b/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala @@ -18,4 +18,13 @@ class PhantomJSTest extends JSEnvTest with ComTests { } + @Test + def syntaxErrorTest = { + + """ + { + """.fails() + + } + } From 0deaef69d79cde0a3e00dbc9d2fa9878f9d4539c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 29 May 2015 18:35:06 +0200 Subject: [PATCH 51/84] Fix a bunch of style issues discovered by Scalastyle. --- .../org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala | 2 +- .../scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala | 3 ++- .../test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala | 6 +++--- .../scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala index f4b05b53b..e19267989 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala @@ -118,7 +118,7 @@ final class JettyWebsocketManager( def localPort: Int = connector.getLocalPort() - def sendMessage(msg: String) = synchronized { + def sendMessage(msg: String): Unit = synchronized { if (webSocketConn != null) webSocketConn.sendMessage(msg) } diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index 786b9d86a..1bd5323ea 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -422,7 +422,8 @@ class PhantomJSEnv( | if (trace && trace.length) { | console.error(''); | trace.forEach(function(t) { - | console.error(' ' + t.file + ':' + t.line + (t.function ? ' (in function "' + t.function +'")' : '')); + | console.error(' ' + t.file + ':' + t.line + + | (t.function ? ' (in function "' + t.function +'")' : '')); | }); | } | diff --git a/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala b/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala index a9a957348..b5304f1fc 100644 --- a/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala +++ b/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala @@ -6,10 +6,10 @@ import org.junit.Test class PhantomJSTest extends JSEnvTest with ComTests { - protected def newJSEnv = new PhantomJSEnv + protected def newJSEnv: PhantomJSEnv = new PhantomJSEnv @Test - def failureTest = { + def failureTest: Unit = { """ var a = {}; @@ -19,7 +19,7 @@ class PhantomJSTest extends JSEnvTest with ComTests { } @Test - def syntaxErrorTest = { + def syntaxErrorTest: Unit = { """ { diff --git a/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala b/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala index 959f17928..47a699ad3 100644 --- a/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala +++ b/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala @@ -19,7 +19,7 @@ class RetryingComJSEnvTest extends JSEnvTest with ComTests { override protected def logger: Logger = NullLogger - protected def newJSEnv = { + protected def newJSEnv: RetryingComJSEnv = { val baseEnv = new RhinoJSEnv(Semantics.Defaults) new RetryingComJSEnv(new FailingEnv(baseEnv), maxFails) } @@ -52,7 +52,7 @@ class RetryingComJSEnvTest extends JSEnvTest with ComTests { private class FailingComJSRunner(baseRunner: ComJSRunner) extends DummyJSRunner with ComJSRunner { - def future = baseRunner.future + def future: Future[Unit] = baseRunner.future def send(msg: String): Unit = { maybeFail() From 37f232204f194af76853dd9760bd6eb29172d430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 29 May 2015 18:35:24 +0200 Subject: [PATCH 52/84] Add Scalastyle with a basic configuration. At least, this should catch quite a few trivial errors from here on. --- .../main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index 1bd5323ea..f5a7dd6b7 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -337,6 +337,7 @@ class PhantomJSEnv( * https://github.com/ariya/phantomjs/issues/10522 */ override protected def initFiles(): Seq[VirtualJSFile] = Seq( + // scalastyle:off line.size.limit new MemVirtualJSFile("bindPolyfill.js").withContent( """ |// Polyfill for Function.bind from Facebook react: @@ -391,6 +392,7 @@ class PhantomJSEnv( |}; """.stripMargin ) + // scalastyle:on line.size.limit ) protected def writeWebpageLauncher(out: Writer): Unit = { From 88cd9f5697aa5e7995943adb7df7e47568a2e266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 25 Jun 2015 11:13:15 +0200 Subject: [PATCH 53/84] Fix scala-js/scala-js#1752: Build for and upgrade to Scala 2.11.7. --- sbt-plugin-test/build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 9804a89f8..8abbf59c6 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -6,7 +6,7 @@ version := scalaJSVersion val versionSettings = Seq( version := scalaJSVersion, - scalaVersion := "2.11.6" + scalaVersion := "2.11.7" ) val baseSettings = versionSettings ++ Seq( @@ -16,7 +16,7 @@ val baseSettings = versionSettings ++ Seq( lazy val referencedCrossProjectJS = ProjectRef(file("referencedCrossProject"), "referencedCrossProjectJS") lazy val referencedCrossProjectJVM = ProjectRef(file("referencedCrossProject"), "referencedCrossProjectJVM") - + lazy val root = project.in(file(".")). aggregate(noDOM, withDOM, multiTestJS, multiTestJVM, referencedCrossProjectJS, referencedCrossProjectJVM) From c5a213d36217d0e7b04b38781262ab466f0a24be Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 26 Aug 2015 15:06:51 +0200 Subject: [PATCH 54/84] Fix scala-js/scala-js#1845: Add scalaJSOutputWrapper sbt setting. --- sbt-plugin-test/build.sbt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 8abbf59c6..fc655602f 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -23,7 +23,10 @@ lazy val root = project.in(file(".")). lazy val noDOM = project.settings(baseSettings: _*). enablePlugins(ScalaJSPlugin). settings( - name := "Scala.js sbt test w/o DOM" + name := "Scala.js sbt test w/o DOM", + scalaJSOutputWrapper := ( + "// Scala.js - noDOM sbt test\n//\n// Compiled with Scala.js\n", + "// End of Scala.js generated script") ) lazy val withDOM = project.settings(baseSettings: _*). @@ -32,8 +35,10 @@ lazy val withDOM = project.settings(baseSettings: _*). name := "Scala.js sbt test w/ DOM", jsDependencies ++= Seq( RuntimeDOM, - "org.webjars" % "jquery" % "1.10.2" / "jquery.js" - ) + "org.webjars" % "jquery" % "1.10.2" / "jquery.js"), + scalaJSOutputWrapper := ( + "// Scala.js - withDOM sbt test\n//\n// Compiled with Scala.js\n", + "// End of Scala.js generated script") ) lazy val jetty9 = project.settings(baseSettings: _*). From e003671847ffd7d6c53bd56149e6a7e72f0ca8c6 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 23 Oct 2015 16:14:36 +0200 Subject: [PATCH 55/84] Fix scala-js/scala-js#1917: Add -Xfatal-warnings for ScalaDoc generation Note that links to the JavaDoc are not yet correct (filed as scala-js/scala-js#1970). --- .../scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala index 5554ee09a..d97554c01 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala @@ -2,12 +2,12 @@ package org.scalajs.jsenv.phantomjs import org.scalajs.core.tools.io.IO -/** A special [[ClassLoader]] to load the Jetty 8 dependency of [[PhantomJSEnv]] - * in a private space. +/** A special [[java.lang.ClassLoader]] to load the Jetty 8 dependency of + * [[PhantomJSEnv]] in a private space. * * It loads everything that belongs to [[JettyWebsocketManager]] itself (while * retrieving the requested class file from its parent. - * For all other classes, it first tries to load them from [[jettyLoader]], + * For all other classes, it first tries to load them from `jettyLoader`, * which should only contain the Jetty 8 classpath. * If this fails, it delegates to its parent. * From 5ef08a7145cf32a1e39f4700581f2caf53631dca Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 7 Oct 2015 13:49:14 +0200 Subject: [PATCH 56/84] Fix scala-js/scala-js#1690: Add JUnit framework and plugin. * Add junit-runntime: * Adapt JUnit classes * Adapt sbt junit-interface * Adapt hamcrest Matchers (partial support) * Custom sbt JUnit test framework. * Add junit-plugin: Transforms classes containing method with @Test to retain data that would usually be accessed through reflection. * Create new sbt confuiguration for plugins that are only added in test configurations ('scala-js-test-plugin'). * Add JUnit framework to test-suite * Add JUnit plugin test in sbt-plugin-test --- sbt-plugin-test/build.sbt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index fc655602f..9a0a8d7ea 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -75,17 +75,21 @@ lazy val testFrameworkJS = testFramework.js lazy val testFrameworkJVM = testFramework.jvm lazy val multiTest = crossProject. + jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin)). settings( testFrameworks ++= Seq( TestFramework("sbttest.framework.DummyFramework"), - TestFramework("inexistent.Foo", "another.strange.Bar") + TestFramework("inexistent.Foo", "another.strange.Bar"), + TestFramework("org.scalajs.jasminetest.JasmineFramework") ) ). jsSettings(baseSettings: _*). jsSettings( name := "Multi test framework test JS", // Make FrameworkDetector resilient to other output - #1572 - jsDependencies in Test += ProvidedJS / "consoleWriter.js" + jsDependencies in Test += ProvidedJS / "consoleWriter.js", + testOptions += Tests.Argument( + TestFramework("com.novocode.junit.JUnitFramework"), "-v", "-a") ). jvmSettings(versionSettings: _*). jvmSettings( From 8be06c9b4b94b6656a0935c98d67b9d583b943db Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 27 Nov 2015 07:23:28 +0100 Subject: [PATCH 57/84] Factor basic JSEnv tests in trait --- .../scalajs/jsenv/test/PhantomJSTest.scala | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala b/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala index b5304f1fc..4149f4f70 100644 --- a/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala +++ b/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala @@ -5,26 +5,5 @@ import org.scalajs.jsenv.phantomjs.PhantomJSEnv import org.junit.Test class PhantomJSTest extends JSEnvTest with ComTests { - protected def newJSEnv: PhantomJSEnv = new PhantomJSEnv - - @Test - def failureTest: Unit = { - - """ - var a = {}; - a.foo(); - """.fails() - - } - - @Test - def syntaxErrorTest: Unit = { - - """ - { - """.fails() - - } - } From 12892943468ac9f76540dcb196a11218622b05e9 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 27 Nov 2015 07:25:12 +0100 Subject: [PATCH 58/84] Fix scala-js/scala-js#2053: Always use UTF8 in PhantomJSEnv --- .../scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index f5a7dd6b7..43e3a547b 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -396,7 +396,9 @@ class PhantomJSEnv( ) protected def writeWebpageLauncher(out: Writer): Unit = { - out.write("\n\nPhantom.js Launcher\n") + out.write(s""" + Phantom.js Launcher + """) sendJS(getLibJSFiles(), out) writeCodeLauncher(code, out) out.write("\n\n\n") @@ -466,7 +468,9 @@ class PhantomJSEnv( val webTmpF = File.createTempFile("phantomjs-launcher-webpage", ".html") webTmpF.deleteOnExit() - val out = new BufferedWriter(new FileWriter(webTmpF)) + val out = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(webTmpF), "UTF-8")) + try { writeWebpageLauncher(out) } finally { From f18e504e9efbd43c483ab3997546016aeeeeaf96 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 17 Jan 2016 11:58:33 +0100 Subject: [PATCH 59/84] Tools refactoring This commit substantially refactors Scala.js' linker. Notably: Linker pipeline --------------- The linker pipeline is divided into a frontend and a backend. The frontend links and optimizes IR files into a `LinkingUnit`. It paves the path for linker plugins in the future. The backend emits a linking unit into a JavaScript file. Currently there are two backends: * BasicLinkerBackend (basic JavaScript generation, aka fastOptJS) * ClosureBackend (optimization pass with the Google Closure compiler, aka fullOptJS) As a side effect of this change, Rhino now runs optimized code (unless this is explicitly disabled). This further has the side effect, that the `PreLinkStage` was entirely removed. Classpath traversal ------------------- Classpath traversal and abstraction is fully pushed to the build system. As a consequence, the whole `org.scalajs.core.tools.classpath` package is removed. Functionally is mostly replaced by a couple of lines of sbt code. This also fixes scala-js/scala-js#1635. IR caching ---------- In the past, each linker cached its own IR. This caused a lot of IR to be in memory multiple times. This commit replaces caching with a global IR cache. There are two main advantages: * IR for each class is only cached once in memory. * Linking other projects or configurations (e.g. compile v.s. test) can reuse the already cached IR. JSEnvs ------ Since classpaths have been removed, we need a way to re-enable lazy loading in Rhino. We therefore introduce the `LinkingUnitJSEnv` that can directly accept a `LinkingUnit`. Further, we enrich the `JSEnv`s with an API to load libraries. This allows for much nicer interfaces in a couple of spots, where you essentially just pass a `JSEnv` and then run your code in it, with all dependencies already present (e.g. FrameworkDetector). There are a couple of advantages: - Symbol requirements are made explicit - No duplicate code for linking in RhinoJSEnv - Rhino can now run with optimized code (and does so by default) --- .../org/scalajs/jsenv/RetryingComJSEnv.scala | 49 +++++++++-------- .../jsenv/phantomjs/PhantomJSEnv.scala | 52 ++++++++++--------- .../jsenv/test/RetryingComJSEnvTest.scala | 43 ++++++++------- sbt-plugin-test/build.sbt | 6 +-- sbt-plugin-test/project/Jetty9Test.scala | 7 ++- 5 files changed, 84 insertions(+), 73 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala index 3d33b1026..9c879332d 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala @@ -10,8 +10,8 @@ package org.scalajs.jsenv import org.scalajs.core.tools.io._ -import org.scalajs.core.tools.classpath._ -import org.scalajs.core.tools.logging._ +import org.scalajs.core.tools.logging.Logger +import org.scalajs.core.tools.jsdep.ResolvedJSDependency import scala.concurrent.{Future, Promise, ExecutionContext} import scala.concurrent.duration.Duration @@ -36,19 +36,21 @@ final class RetryingComJSEnv(val baseEnv: ComJSEnv, def this(baseEnv: ComJSEnv) = this(baseEnv, 5) - def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): JSRunner = { - baseEnv.jsRunner(classpath, code, logger, console) + def name: String = s"Retrying ${baseEnv.name}" + + def jsRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile): JSRunner = { + baseEnv.jsRunner(libs, code) } - def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): AsyncJSRunner = { - baseEnv.asyncRunner(classpath, code, logger, console) + def asyncRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile): AsyncJSRunner = { + baseEnv.asyncRunner(libs, code) } - def comRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): ComJSRunner = { - new RetryingComJSRunner(classpath, code, logger, console) + def comRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile): ComJSRunner = { + new RetryingComJSRunner(libs, code) } /** Hack to work around abstract override in ComJSRunner */ @@ -56,24 +58,29 @@ final class RetryingComJSEnv(val baseEnv: ComJSEnv, def stop(): Unit = () } - private class RetryingComJSRunner(classpath: CompleteClasspath, - code: VirtualJSFile, logger: Logger, - console: JSConsole) extends DummyJSRunner with ComJSRunner { + private class RetryingComJSRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile) extends DummyJSRunner with ComJSRunner { private[this] val promise = Promise[Unit] - private[this] var curRunner = - baseEnv.comRunner(classpath, code, logger, console) + private[this] var curRunner = baseEnv.comRunner(libs, code) private[this] var hasReceived = false private[this] var retryCount = 0 private[this] val log = mutable.Buffer.empty[LogItem] + private[this] var _logger: Logger = _ + private[this] var _console: JSConsole = _ + def future: Future[Unit] = promise.future - def start(): Future[Unit] = { + def start(logger: Logger, console: JSConsole): Future[Unit] = { require(log.isEmpty, "start() may only be called once") + + _logger = logger + _console = console + logAndDo(Start) future } @@ -124,16 +131,16 @@ final class RetryingComJSEnv(val baseEnv: ComJSEnv, if (hasReceived || retryCount > maxRetries || promise.isCompleted) throw cause - logger.warn("Retrying to launch a " + baseEnv.getClass.getName + + _logger.warn("Retrying to launch a " + baseEnv.getClass.getName + " after " + cause.toString) val oldRunner = curRunner curRunner = try { - baseEnv.comRunner(classpath, code, logger, console) + baseEnv.comRunner(libs, code) } catch { case NonFatal(t) => - logger.error("Could not retry: creating an new runner failed: " + + _logger.error("Could not retry: creating an new runner failed: " + t.toString) throw cause } @@ -164,7 +171,7 @@ final class RetryingComJSEnv(val baseEnv: ComJSEnv, case Start => import ExecutionContext.Implicits.global val runner = curRunner - runner.start() onComplete { result => + runner.start(_logger, _console) onComplete { result => // access to curRunner and promise must be synchronized synchronized { if (curRunner eq runner) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index 43e3a547b..534788a23 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -15,7 +15,7 @@ import org.scalajs.jsenv.Utils.OptDeadline import org.scalajs.core.ir.Utils.{escapeJS, fixFileURI} import org.scalajs.core.tools.io._ -import org.scalajs.core.tools.classpath._ +import org.scalajs.core.tools.jsdep.ResolvedJSDependency import org.scalajs.core.tools.logging._ import java.io.{ Console => _, _ } @@ -25,7 +25,7 @@ import scala.io.Source import scala.collection.mutable import scala.annotation.tailrec -import scala.concurrent.{ExecutionContext, TimeoutException} +import scala.concurrent.{ExecutionContext, TimeoutException, Future} import scala.concurrent.duration.Duration class PhantomJSEnv( @@ -41,35 +41,32 @@ class PhantomJSEnv( protected def vmName: String = "PhantomJS" protected def executable: String = phantomjsPath - override def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): JSRunner = { - new PhantomRunner(classpath, code, logger, console) + override def jsRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile): JSRunner = { + new PhantomRunner(libs, code) } - override def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): AsyncJSRunner = { - new AsyncPhantomRunner(classpath, code, logger, console) + override def asyncRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile): AsyncJSRunner = { + new AsyncPhantomRunner(libs, code) } - override def comRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): ComJSRunner = { - new ComPhantomRunner(classpath, code, logger, console) + override def comRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile): ComJSRunner = { + new ComPhantomRunner(libs, code) } - protected class PhantomRunner(classpath: CompleteClasspath, - code: VirtualJSFile, logger: Logger, console: JSConsole - ) extends ExtRunner(classpath, code, logger, console) - with AbstractPhantomRunner + protected class PhantomRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile) extends ExtRunner(libs, code) + with AbstractPhantomRunner - protected class AsyncPhantomRunner(classpath: CompleteClasspath, - code: VirtualJSFile, logger: Logger, console: JSConsole - ) extends AsyncExtRunner(classpath, code, logger, console) - with AbstractPhantomRunner + protected class AsyncPhantomRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile) extends AsyncExtRunner(libs, code) + with AbstractPhantomRunner - protected class ComPhantomRunner(classpath: CompleteClasspath, - code: VirtualJSFile, logger: Logger, console: JSConsole - ) extends AsyncPhantomRunner(classpath, code, logger, console) - with ComJSRunner with WebsocketListener { + protected class ComPhantomRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile) extends AsyncPhantomRunner(libs, code) + with ComJSRunner with WebsocketListener { private def loadMgr() = { val loader = @@ -111,8 +108,6 @@ class PhantomJSEnv( private[this] val recvBuf = mutable.Queue.empty[String] private[this] val fragmentsBuf = new StringBuilder - mgr.start() - private def comSetup = { def maybeExit(code: Int) = if (autoExit) @@ -230,6 +225,13 @@ class PhantomJSEnv( new MemVirtualJSFile("comSetup.js").withContent(code) } + override def start(logger: Logger, console: JSConsole): Future[Unit] = { + setupLoggerAndConsole(logger, console) + mgr.start() + startExternalJSEnv() + future + } + def send(msg: String): Unit = synchronized { if (awaitConnection()) { val fragParts = msg.length / MaxCharPayloadSize diff --git a/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala b/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala index 47a699ad3..0be3e53c1 100644 --- a/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala +++ b/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala @@ -3,10 +3,11 @@ package org.scalajs.jsenv.test import org.scalajs.jsenv.rhino.RhinoJSEnv import org.scalajs.jsenv._ -import org.scalajs.core.tools.io._ -import org.scalajs.core.tools.classpath._ +import org.scalajs.core.tools.io.VirtualJSFile +import org.scalajs.core.tools.jsdep.ResolvedJSDependency +import org.scalajs.core.tools.linker.LinkingUnit import org.scalajs.core.tools.logging._ -import org.scalajs.core.tools.sem._ +import org.scalajs.core.tools.sem.Semantics import scala.concurrent.Future import scala.concurrent.duration.Duration @@ -17,31 +18,33 @@ class RetryingComJSEnvTest extends JSEnvTest with ComTests { private final val maxFails = 5 - override protected def logger: Logger = NullLogger - - protected def newJSEnv: RetryingComJSEnv = { - val baseEnv = new RhinoJSEnv(Semantics.Defaults) - new RetryingComJSEnv(new FailingEnv(baseEnv), maxFails) + // Don't log anything here + override protected def start(runner: AsyncJSRunner): Future[Unit] = { + runner.start(NullLogger, ConsoleJSConsole) } + protected def newJSEnv: RetryingComJSEnv = + new RetryingComJSEnv(new FailingEnv(new RhinoJSEnv), maxFails) + private final class FailingEnv(baseEnv: ComJSEnv) extends ComJSEnv { + def name: String = s"FailingJSEnv of ${baseEnv.name}" + private[this] var fails = 0 private[this] var failedReceive = false - def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): JSRunner = { - baseEnv.jsRunner(classpath, code, logger, console) + def jsRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile): JSRunner = { + baseEnv.jsRunner(libs, code) } - def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): AsyncJSRunner = { - baseEnv.asyncRunner(classpath, code, logger, console) + def asyncRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile): AsyncJSRunner = { + baseEnv.asyncRunner(libs, code) } - def comRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): ComJSRunner = { - new FailingComJSRunner( - baseEnv.comRunner(classpath, code, logger, console)) + def comRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile): ComJSRunner = { + new FailingComJSRunner(baseEnv.comRunner(libs, code)) } /** Hack to work around abstract override in ComJSRunner */ @@ -67,9 +70,9 @@ class RetryingComJSEnvTest extends JSEnvTest with ComTests { baseRunner.receive(timeout) } - def start(): Future[Unit] = { + def start(logger: Logger, console: JSConsole): Future[Unit] = { maybeFail() - baseRunner.start() + baseRunner.start(logger, console) } override def stop(): Unit = { diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 9a0a8d7ea..b7e275f4f 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -129,8 +129,8 @@ lazy val jsDependenciesTest = project.settings(versionSettings: _*). settings(inConfig(Compile)(Seq( packageJSDependencies <<= packageJSDependencies.dependsOn(Def.task { // perform verifications on the ordering and deduplications - val cp = scalaJSPreLinkClasspath.value - val relPaths = cp.jsLibs.map(_.info.relPath) + val resolvedDeps = resolvedJSDependencies.value.data + val relPaths = resolvedDeps.map(_.info.relPath) assert(relPaths.toSet == Set( "META-INF/resources/webjars/mustachejs/0.8.2/mustache.js", @@ -143,7 +143,7 @@ lazy val jsDependenciesTest = project.settings(versionSettings: _*). "js/customJQuery/jquery.js"), s"Bad set of relPathes: ${relPaths.toSet}") - val minifiedRelPaths = cp.jsLibs.flatMap(_.info.relPathMinified) + val minifiedRelPaths = resolvedDeps.flatMap(_.info.relPathMinified) assert(minifiedRelPaths.toSet == Set( "META-INF/resources/webjars/immutable/3.4.0/immutable.min.js"), diff --git a/sbt-plugin-test/project/Jetty9Test.scala b/sbt-plugin-test/project/Jetty9Test.scala index 9d2a3d33b..809b1bd94 100644 --- a/sbt-plugin-test/project/Jetty9Test.scala +++ b/sbt-plugin-test/project/Jetty9Test.scala @@ -21,8 +21,7 @@ object Jetty9Test { private val jettyPort = 23548 val runSetting = run <<= Def.inputTask { - val env = (jsEnv in Compile).value.asInstanceOf[ComJSEnv] - val cp = (scalaJSExecClasspath in Compile).value + val jsEnv = (loadedJSEnv in Compile).value.asInstanceOf[ComJSEnv] val jsConsole = scalaJSConsole.value val code = new MemVirtualJSFile("runner.js").withContent( @@ -43,9 +42,9 @@ object Jetty9Test { """ ) - val runner = env.comRunner(cp, code, streams.value.log, jsConsole) + val runner = jsEnv.comRunner(code) - runner.start() + runner.start(streams.value.log, jsConsole) val jetty = setupJetty((resourceDirectory in Compile).value) From af9f021c8551eef44dcb02ef0b7bd767bc3508af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 17 Jan 2016 21:06:32 +0100 Subject: [PATCH 60/84] Make private everything that can be in tools and js-envs. --- .../phantomjs/JettyWebsocketManager.scala | 2 +- .../jsenv/phantomjs/PhantomJSEnv.scala | 49 +++++++++++-------- .../phantomjs/PhantomJettyClassLoader.scala | 4 +- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala index e19267989..292a632b0 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala @@ -8,7 +8,7 @@ import org.eclipse.jetty.websocket.{WebSocket, WebSocketHandler} import org.eclipse.jetty.util.component.{LifeCycle, AbstractLifeCycle} import org.eclipse.jetty.util.log -final class JettyWebsocketManager( +private[phantomjs] final class JettyWebsocketManager( wsListener: WebsocketListener) extends WebsocketManager { thisMgr => private[this] var webSocketConn: WebSocket.Connection = null diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index 534788a23..c63ba5a55 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -66,7 +66,31 @@ class PhantomJSEnv( protected class ComPhantomRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile) extends AsyncPhantomRunner(libs, code) - with ComJSRunner with WebsocketListener { + with ComJSRunner { + + private var mgrIsRunning: Boolean = false + + private object websocketListener extends WebsocketListener { // scalastyle:ignore + def onRunning(): Unit = ComPhantomRunner.this.synchronized { + mgrIsRunning = true + ComPhantomRunner.this.notifyAll() + } + + def onOpen(): Unit = ComPhantomRunner.this.synchronized { + ComPhantomRunner.this.notifyAll() + } + + def onClose(): Unit = ComPhantomRunner.this.synchronized { + ComPhantomRunner.this.notifyAll() + } + + def onMessage(msg: String): Unit = ComPhantomRunner.this.synchronized { + recvBuf.enqueue(msg) + ComPhantomRunner.this.notifyAll() + } + + def log(msg: String): Unit = logger.debug(s"PhantomJS WS Jetty: $msg") + } private def loadMgr() = { val loader = @@ -79,32 +103,15 @@ class PhantomJSEnv( val ctors = clazz.getConstructors() assert(ctors.length == 1, "JettyWebsocketManager may only have one ctor") - val mgr = ctors.head.newInstance(this) + val mgr = ctors.head.newInstance(websocketListener) mgr.asInstanceOf[WebsocketManager] } - val mgr: WebsocketManager = loadMgr() - - protected var mgrIsRunning: Boolean = false - - def onRunning(): Unit = synchronized { - mgrIsRunning = true - notifyAll() - } - - def onOpen(): Unit = synchronized(notifyAll()) - def onClose(): Unit = synchronized(notifyAll()) + private val mgr: WebsocketManager = loadMgr() future.onComplete(_ => synchronized(notifyAll()))(ExecutionContext.global) - def onMessage(msg: String): Unit = synchronized { - recvBuf.enqueue(msg) - notifyAll() - } - - def log(msg: String): Unit = logger.debug(s"PhantomJS WS Jetty: $msg") - private[this] val recvBuf = mutable.Queue.empty[String] private[this] val fragmentsBuf = new StringBuilder @@ -506,7 +513,7 @@ class PhantomJSEnv( } -object PhantomJSEnv { +private object PhantomJSEnv { private final val MaxByteMessageSize = 32768 // 32 KB private final val MaxCharMessageSize = MaxByteMessageSize / 2 // 2B per char private final val MaxCharPayloadSize = MaxCharMessageSize - 1 // frag flag diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala index d97554c01..428279e4c 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala @@ -5,13 +5,13 @@ import org.scalajs.core.tools.io.IO /** A special [[java.lang.ClassLoader]] to load the Jetty 8 dependency of * [[PhantomJSEnv]] in a private space. * - * It loads everything that belongs to [[JettyWebsocketManager]] itself (while + * It loads everything that belongs to `JettyWebsocketManager` itself (while * retrieving the requested class file from its parent. * For all other classes, it first tries to load them from `jettyLoader`, * which should only contain the Jetty 8 classpath. * If this fails, it delegates to its parent. * - * The rationale is, that [[JettyWebsocketManager]] and its dependees can use + * The rationale is, that `JettyWebsocketManager` and its dependees can use * the classes on the Jetty 8 classpath, while they remain hidden from the rest * of the Java world. This allows to load another version of Jetty in the same * JVM for the rest of the project. From 0216ccbf962090c2c564d07adb3ee517b76ded64 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 26 Jan 2016 14:52:42 +0900 Subject: [PATCH 61/84] Fix scala-js/scala-js#2202: Prevent concurrent use of linker in sbt We use sbt's tag mechanism to prevent concurrent execution. The test failed 60 consecutive times before the change and succeeded 60 consecutive times after it on a 2 core machine. --- sbt-plugin-test/build.sbt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index b7e275f4f..2f89b6063 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -27,7 +27,11 @@ lazy val noDOM = project.settings(baseSettings: _*). scalaJSOutputWrapper := ( "// Scala.js - noDOM sbt test\n//\n// Compiled with Scala.js\n", "// End of Scala.js generated script") - ) + ). + /* This hopefully exposes concurrent uses of the linker. If it fails/gets + * flaky, there is a bug somewhere - #2202 + */ + settings(inConfig(Compile)(run <<= run.dependsOn(fastOptJS, loadedJSEnv)): _*) lazy val withDOM = project.settings(baseSettings: _*). enablePlugins(ScalaJSPlugin). From a9062ec5cf3fc701d3158a196f7a727f65df1d79 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 26 Jan 2016 09:08:33 +0900 Subject: [PATCH 62/84] Fix scala-js/scala-js#2198: Allow non-existent classpath entries --- sbt-plugin-test/build.sbt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index b7e275f4f..2465963d6 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -11,7 +11,11 @@ val versionSettings = Seq( val baseSettings = versionSettings ++ Seq( libraryDependencies += - "org.scala-js" %% "scalajs-jasmine-test-framework" % scalaJSVersion % "test" + "org.scala-js" %% "scalajs-jasmine-test-framework" % scalaJSVersion % "test", + + // Test that non-existent classpath entries are allowed - #2198 + fullClasspath in Compile += (baseDirectory in "root").value / + "non-existent-directory-please-dont-ever-create-this" ) lazy val referencedCrossProjectJS = ProjectRef(file("referencedCrossProject"), "referencedCrossProjectJS") From b11efdc6e4d1a7db2f155b1632610c09155e09a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 6 Feb 2016 15:48:26 +0100 Subject: [PATCH 63/84] Fix scala-js/scala-js#2005: Add cross-version shared source directories. --- sbt-plugin-test/build.sbt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 8e42f4612..1b12e8fbc 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -105,6 +105,17 @@ lazy val multiTest = crossProject. libraryDependencies += "com.novocode" % "junit-interface" % "0.9" % "test" ). + settings( + // Scala cross-version support for shared source directory - #2005 + unmanagedSourceDirectories in Compile := { + val srcDirs = (unmanagedSourceDirectories in Compile).value + val version = scalaBinaryVersion.value + val expected = + (baseDirectory.value.getParentFile / "shared" / "src" / "main" / s"scala-$version").getPath + assert(srcDirs.exists(_.getPath == expected)) + srcDirs + } + ). dependsOn(testFramework % "test") lazy val multiTestJS = multiTest.js From 309de30cf170ed234d9a170eba5b2c4ab6bbd0ed Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 11 Feb 2016 14:49:16 +0100 Subject: [PATCH 64/84] Fix scala-js/scala-js#2226: Remove timeout from PhantomJSEnv socket connection. --- .../org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala index 292a632b0..0d5019fca 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala @@ -47,6 +47,7 @@ private[phantomjs] final class JettyWebsocketManager( thisMgr.synchronized { if (isConnected) throw new IllegalStateException("Client connected twice") + connection.setMaxIdleTime(Int.MaxValue) webSocketConn = connection } wsListener.onOpen() From eafa7f386d8c960f09217f7a3da6cba2cfb2d08c Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 9 Mar 2016 12:56:47 +0100 Subject: [PATCH 65/84] Fix scala-js/scala-js#2243: fix JSDependencies cache path. --- sbt-plugin-test/build.sbt | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 1b12e8fbc..86e9ad828 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -18,6 +18,22 @@ val baseSettings = versionSettings ++ Seq( "non-existent-directory-please-dont-ever-create-this" ) +val regressionTestForIssue2243 = TaskKey[Unit]("regressionTestForIssue2243", + "", KeyRanks.BTask) + +def withRegretionTestForIssue2243(project: Project): Project = { + project.settings(inConfig(Compile)(Seq( + regressionTestForIssue2243 := { + // Regression test for issue #2243 + val _ = Def.sequential(packageJSDependencies in Compile, + packageMinifiedJSDependencies in Compile).value + assert((artifactPath in(Compile, packageJSDependencies)).value.exists) + assert((artifactPath in(Compile, packageMinifiedJSDependencies)).value.exists) + streams.value.log.info("Regression test for issue #2243 passed") + } + )): _*) +} + lazy val referencedCrossProjectJS = ProjectRef(file("referencedCrossProject"), "referencedCrossProjectJS") lazy val referencedCrossProjectJVM = ProjectRef(file("referencedCrossProject"), "referencedCrossProjectJVM") @@ -121,7 +137,8 @@ lazy val multiTest = crossProject. lazy val multiTestJS = multiTest.js lazy val multiTestJVM = multiTest.jvm -lazy val jsDependenciesTest = project.settings(versionSettings: _*). +lazy val jsDependenciesTest = withRegretionTestForIssue2243( + project.settings(versionSettings: _*). enablePlugins(ScalaJSPlugin). settings( jsDependencies ++= Seq( @@ -185,6 +202,12 @@ lazy val jsDependenciesTest = project.settings(versionSettings: _*). }) )): _*). dependsOn(jetty9) // depends on jQuery +) + +lazy val jsNoDependenciesTest = withRegretionTestForIssue2243( + project.settings(versionSettings: _*). + enablePlugins(ScalaJSPlugin) +) // Test %%% macro - #1331 val unusedSettings = Seq( From eeb1289bc354c7f7d8796d222c6a3b96b1b24fa2 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 14 Mar 2016 14:07:53 +0100 Subject: [PATCH 66/84] Fix scala-js/scala-js#2284: Upgrade to 2.11.8. --- sbt-plugin-test/build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 86e9ad828..643d262ce 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -6,7 +6,7 @@ version := scalaJSVersion val versionSettings = Seq( version := scalaJSVersion, - scalaVersion := "2.11.7" + scalaVersion := "2.11.8" ) val baseSettings = versionSettings ++ Seq( From 7959ce2e7067dfc84705fcfaab5ed136fca8ed44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 24 Apr 2016 20:37:15 +0200 Subject: [PATCH 67/84] Fix scala-js/scala-js#2350: Add an sbt setting isScalaJSProject. This sbt setting is true if and only if the current project is a Scala.js project. It is not meant to be written to by users (only to be read). --- sbt-plugin-test/build.sbt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 643d262ce..0620fbee6 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -113,13 +113,25 @@ lazy val multiTest = crossProject. // Make FrameworkDetector resilient to other output - #1572 jsDependencies in Test += ProvidedJS / "consoleWriter.js", testOptions += Tests.Argument( - TestFramework("com.novocode.junit.JUnitFramework"), "-v", "-a") + TestFramework("com.novocode.junit.JUnitFramework"), "-v", "-a"), + + // Test isScalaJSProject (as a setting, it's evaluated when loading the build) + isScalaJSProject ~= { value => + assert(value, "isScalaJSProject should be true in multiTestJS") + value + } ). jvmSettings(versionSettings: _*). jvmSettings( name := "Multi test framework test JVM", libraryDependencies += - "com.novocode" % "junit-interface" % "0.9" % "test" + "com.novocode" % "junit-interface" % "0.9" % "test", + + // Test isScalaJSProject (as a setting, it's evaluated when loading the build) + isScalaJSProject ~= { value => + assert(!value, "isScalaJSProject should be true in multiTestJVM") + value + } ). settings( // Scala cross-version support for shared source directory - #2005 From b9b9f87c9ada55b684898f8bc23e84f73e1f48c9 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 26 Apr 2016 11:22:01 +0200 Subject: [PATCH 68/84] Remove Jasmine from sbtPluginTest. --- sbt-plugin-test/build.sbt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 0620fbee6..9aee9b786 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -10,8 +10,8 @@ val versionSettings = Seq( ) val baseSettings = versionSettings ++ Seq( - libraryDependencies += - "org.scala-js" %% "scalajs-jasmine-test-framework" % scalaJSVersion % "test", + testOptions += Tests.Argument( + TestFramework("com.novocode.junit.JUnitFramework"), "-v", "-a"), // Test that non-existent classpath entries are allowed - #2198 fullClasspath in Compile += (baseDirectory in "root").value / @@ -42,6 +42,7 @@ lazy val root = project.in(file(".")). lazy val noDOM = project.settings(baseSettings: _*). enablePlugins(ScalaJSPlugin). + enablePlugins(ScalaJSJUnitPlugin). settings( name := "Scala.js sbt test w/o DOM", scalaJSOutputWrapper := ( @@ -55,6 +56,7 @@ lazy val noDOM = project.settings(baseSettings: _*). lazy val withDOM = project.settings(baseSettings: _*). enablePlugins(ScalaJSPlugin). + enablePlugins(ScalaJSJUnitPlugin). settings( name := "Scala.js sbt test w/ DOM", jsDependencies ++= Seq( @@ -103,8 +105,7 @@ lazy val multiTest = crossProject. settings( testFrameworks ++= Seq( TestFramework("sbttest.framework.DummyFramework"), - TestFramework("inexistent.Foo", "another.strange.Bar"), - TestFramework("org.scalajs.jasminetest.JasmineFramework") + TestFramework("inexistent.Foo", "another.strange.Bar") ) ). jsSettings(baseSettings: _*). @@ -112,8 +113,6 @@ lazy val multiTest = crossProject. name := "Multi test framework test JS", // Make FrameworkDetector resilient to other output - #1572 jsDependencies in Test += ProvidedJS / "consoleWriter.js", - testOptions += Tests.Argument( - TestFramework("com.novocode.junit.JUnitFramework"), "-v", "-a"), // Test isScalaJSProject (as a setting, it's evaluated when loading the build) isScalaJSProject ~= { value => From 60877a566ec27cb08a2b9ddc0fc68f37cd143bca Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 25 Apr 2016 11:25:39 +0200 Subject: [PATCH 69/84] Fix scala-js/scala-js#2250: Split jsEnvs, creating jsEnvsTestKit, jsEnvsTestSuite. This will make it possible to publish jsEnvsTestKit (issue scala-js/scala-js#2250) to be able to test external jsEnvs like SeleniumJSEnv. jsEnvsTestSuite contains the test implementations of jsEnvs implemented in scala-js/scala-js. --- .../scala/org/scalajs/jsenv/test/PhantomJSTest.scala | 2 -- .../org/scalajs/jsenv/test/RetryingComJSEnvTest.scala | 9 ++------- 2 files changed, 2 insertions(+), 9 deletions(-) rename {js-envs => js-envs-test-suite}/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala (89%) rename {js-envs => js-envs-test-suite}/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala (94%) diff --git a/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala b/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala similarity index 89% rename from js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala rename to js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala index 4149f4f70..b4fd37df0 100644 --- a/js-envs/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala +++ b/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala @@ -2,8 +2,6 @@ package org.scalajs.jsenv.test import org.scalajs.jsenv.phantomjs.PhantomJSEnv -import org.junit.Test - class PhantomJSTest extends JSEnvTest with ComTests { protected def newJSEnv: PhantomJSEnv = new PhantomJSEnv } diff --git a/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala b/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala similarity index 94% rename from js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala rename to js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala index 0be3e53c1..50b96d5f2 100644 --- a/js-envs/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala +++ b/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala @@ -1,19 +1,14 @@ package org.scalajs.jsenv.test -import org.scalajs.jsenv.rhino.RhinoJSEnv -import org.scalajs.jsenv._ - import org.scalajs.core.tools.io.VirtualJSFile import org.scalajs.core.tools.jsdep.ResolvedJSDependency -import org.scalajs.core.tools.linker.LinkingUnit import org.scalajs.core.tools.logging._ -import org.scalajs.core.tools.sem.Semantics +import org.scalajs.jsenv.rhino.RhinoJSEnv +import org.scalajs.jsenv.{ComJSRunner, JSConsole, _} import scala.concurrent.Future import scala.concurrent.duration.Duration -import org.junit.Test - class RetryingComJSEnvTest extends JSEnvTest with ComTests { private final val maxFails = 5 From 5fb205e19ce7b4d82a185b9b7855770596c54c4b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 2 May 2016 13:59:55 +0200 Subject: [PATCH 70/84] Fix scala-js/scala-js#2375: Move Node and Phantom CustomInitFilesTest to jsEnvTestSuite. --- .../jsenv/test/PhantomJSWithCustomInitFilesTest.scala | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/PhantomJSWithCustomInitFilesTest.scala diff --git a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/PhantomJSWithCustomInitFilesTest.scala b/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/PhantomJSWithCustomInitFilesTest.scala new file mode 100644 index 000000000..8c31699b4 --- /dev/null +++ b/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/PhantomJSWithCustomInitFilesTest.scala @@ -0,0 +1,9 @@ +package org.scalajs.jsenv.test + +import org.scalajs.jsenv.phantomjs.PhantomJSEnv + +class PhantomJSWithCustomInitFilesTest extends CustomInitFilesTest { + protected def newJSEnv: PhantomJSEnv = new PhantomJSEnv { + override def customInitFiles() = makeCustomInitFiles() + } +} From 9110106f6cc38a34fbf3b85e45d92c6a135653d5 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 1 May 2016 23:07:21 +0200 Subject: [PATCH 71/84] Fix scala-js/scala-js#2376: PhantomJSEnv does not escape JS code --- .../jsenv/phantomjs/PhantomJSEnv.scala | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index c63ba5a55..4aaf1a286 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -322,23 +322,18 @@ class PhantomJSEnv( protected trait AbstractPhantomRunner extends AbstractExtRunner { + protected[this] val codeCache = new VirtualFileMaterializer + override protected def getVMArgs() = // Add launcher file to arguments additionalArgs :+ createTmpLauncherFile().getAbsolutePath /** In phantom.js, we include JS using HTML */ override protected def writeJSFile(file: VirtualJSFile, writer: Writer) = { - file match { - case file: FileVirtualJSFile => - val fname = htmlEscape(fixFileURI(file.file.toURI).toASCIIString) - writer.write( - s"""""" + "\n") - case _ => - writer.write("""\n") - } + val realFile = codeCache.materialize(file) + val fname = htmlEscape(fixFileURI(realFile.toURI).toASCIIString) + writer.write( + s"""""" + "\n") } /** @@ -410,7 +405,7 @@ class PhantomJSEnv( """) sendJS(getLibJSFiles(), out) writeCodeLauncher(code, out) - out.write("\n\n\n") + out.write(s"\n\n\n") } protected def createTmpLauncherFile(): File = { @@ -493,13 +488,14 @@ class PhantomJSEnv( } protected def writeCodeLauncher(code: VirtualJSFile, out: Writer): Unit = { - out.write("""\n") + // Create a file with the launcher function. + val launcherFile = new MemVirtualJSFile("phantomjs-launcher.js") + launcherFile.content = s""" + // Phantom.js code launcher + // Origin: ${code.path} + function $launcherName() {${code.content}} + """ + writeJSFile(launcherFile, out) } } @@ -517,4 +513,6 @@ private object PhantomJSEnv { private final val MaxByteMessageSize = 32768 // 32 KB private final val MaxCharMessageSize = MaxByteMessageSize / 2 // 2B per char private final val MaxCharPayloadSize = MaxCharMessageSize - 1 // frag flag + + private final val launcherName = "scalaJSPhantomJSEnvLauncher" } From af2be218633c219745f9e0d7cf6fd09fd94debf8 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 27 Apr 2016 14:27:11 +0200 Subject: [PATCH 72/84] Fix JUnit Scala decoded names. --- sbt-plugin-test/build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 9aee9b786..ddf3e1679 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -11,7 +11,7 @@ val versionSettings = Seq( val baseSettings = versionSettings ++ Seq( testOptions += Tests.Argument( - TestFramework("com.novocode.junit.JUnitFramework"), "-v", "-a"), + TestFramework("com.novocode.junit.JUnitFramework"), "-v", "-a", "-s"), // Test that non-existent classpath entries are allowed - #2198 fullClasspath in Compile += (baseDirectory in "root").value / From a628a79eb5dff1222d770490a112280ea1c35d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 6 Jul 2016 11:19:27 +0200 Subject: [PATCH 73/84] Fix scala-js/scala-js#2494: Store the location of source map files as attributes. The source maps of `fastOptJS` can now be accessed with `fastOptJS.value.get(scalaJSSourceMap).get`, and similarly for `fullOptJS`. --- sbt-plugin-test/build.sbt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index ddf3e1679..dd51eb23d 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -20,6 +20,8 @@ val baseSettings = versionSettings ++ Seq( val regressionTestForIssue2243 = TaskKey[Unit]("regressionTestForIssue2243", "", KeyRanks.BTask) +val testScalaJSSourceMapAttribute = TaskKey[Unit]( + "testScalaJSSourceMapAttribute", "", KeyRanks.BTask) def withRegretionTestForIssue2243(project: Project): Project = { project.settings(inConfig(Compile)(Seq( @@ -118,6 +120,19 @@ lazy val multiTest = crossProject. isScalaJSProject ~= { value => assert(value, "isScalaJSProject should be true in multiTestJS") value + }, + + // Test the scalaJSSourceMap attribute of fastOptJS and fullOptJS + testScalaJSSourceMapAttribute in Test := { + val fastOptFile = (fastOptJS in Test).value + assert(fastOptFile.get(scalaJSSourceMap).exists { + _.getPath == fastOptFile.data.getPath + ".map" + }, "fastOptJS does not have the correct scalaJSSourceMap attribute") + + val fullOptFile = (fullOptJS in Test).value + assert(fullOptFile.get(scalaJSSourceMap).exists { + _.getPath == fullOptFile.data.getPath + ".map" + }, "fullOptJS does not have the correct scalaJSSourceMap attribute") } ). jvmSettings(versionSettings: _*). From c1f2bfefe6d91ec56153365c517523002223c4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 6 Sep 2016 17:53:43 +0200 Subject: [PATCH 74/84] Deprecate RhinoJSEnv without replacement. It will be removed in Scala.js 1.0.0. --- .../scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala b/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala index 50b96d5f2..4aeccda56 100644 --- a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala +++ b/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala @@ -3,7 +3,7 @@ package org.scalajs.jsenv.test import org.scalajs.core.tools.io.VirtualJSFile import org.scalajs.core.tools.jsdep.ResolvedJSDependency import org.scalajs.core.tools.logging._ -import org.scalajs.jsenv.rhino.RhinoJSEnv +import org.scalajs.jsenv.nodejs.NodeJSEnv import org.scalajs.jsenv.{ComJSRunner, JSConsole, _} import scala.concurrent.Future @@ -19,7 +19,7 @@ class RetryingComJSEnvTest extends JSEnvTest with ComTests { } protected def newJSEnv: RetryingComJSEnv = - new RetryingComJSEnv(new FailingEnv(new RhinoJSEnv), maxFails) + new RetryingComJSEnv(new FailingEnv(new NodeJSEnv), maxFails) private final class FailingEnv(baseEnv: ComJSEnv) extends ComJSEnv { def name: String = s"FailingJSEnv of ${baseEnv.name}" From e51a9cba854d88892a757543872d60040a041d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 27 Mar 2017 23:33:01 +0200 Subject: [PATCH 75/84] Separate all PhantomJS-related things in distinct projects. This is part of scala-js/scala-js#2839. * `PhantomJSEnv` and `RetryingComJSEnv` are sent to their own `phantomJSEnv` sbt project. * The settings and tasks used to construct the `ClassLoader` that loads jetty8 are extracted into a separate `AutoPlugin`, named `PhantomJSEnvPlugin`, in the sbt project `phantomJSEnvPlugin`. * The tests in `sbt-plugin-test` do not mix PhantomJS-related tests with other tests anymore (in particular `jsDependencies`). --- .../jsenv/phantomjs/WebsocketListener.scala | 10 -- .../jsenv/phantomjs/WebsocketManager.scala | 10 -- .../phantomjs/JettyWebsocketManager.scala | 8 ++ .../jsenv/phantomjs/PhantomJSEnv.scala | 15 ++- .../phantomjs/PhantomJettyClassLoader.scala | 8 ++ .../jsenv/phantomjs}/RetryingComJSEnv.scala | 23 ++-- .../jsenv/phantomjs/WebsocketListener.scala | 18 ++++ .../jsenv/phantomjs/WebsocketManager.scala | 18 ++++ .../jsenv/phantomjs}/PhantomJSTest.scala | 4 +- .../PhantomJSWithCustomInitFilesTest.scala | 4 +- .../phantomjs}/RetryingComJSEnvTest.scala | 4 +- .../sbtplugin/PhantomJSEnvPlugin.scala | 102 ++++++++++++++++++ sbt-plugin-test/build.sbt | 33 +++--- sbt-plugin-test/project/Jetty9Test.scala | 20 ++-- sbt-plugin-test/project/build.sbt | 3 + 15 files changed, 216 insertions(+), 64 deletions(-) delete mode 100644 js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketListener.scala delete mode 100644 js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketManager.scala rename {js-envs => phantomjs-env}/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala (88%) rename {js-envs => phantomjs-env}/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala (98%) rename {js-envs => phantomjs-env}/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala (81%) rename {js-envs/src/main/scala/org/scalajs/jsenv => phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs}/RetryingComJSEnv.scala (90%) create mode 100644 phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketListener.scala create mode 100644 phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketManager.scala rename {js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test => phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs}/PhantomJSTest.scala (59%) rename {js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test => phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs}/PhantomJSWithCustomInitFilesTest.scala (71%) rename {js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test => phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs}/RetryingComJSEnvTest.scala (97%) create mode 100644 phantomjs-sbt-plugin/src/main/scala/org/scalajs/jsenv/phantomjs/sbtplugin/PhantomJSEnvPlugin.scala diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketListener.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketListener.scala deleted file mode 100644 index b26dc1dcf..000000000 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketListener.scala +++ /dev/null @@ -1,10 +0,0 @@ -package org.scalajs.jsenv.phantomjs - -private[phantomjs] trait WebsocketListener { - def onRunning(): Unit - def onOpen(): Unit - def onClose(): Unit - def onMessage(msg: String): Unit - - def log(msg: String): Unit -} diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketManager.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketManager.scala deleted file mode 100644 index 875393c07..000000000 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketManager.scala +++ /dev/null @@ -1,10 +0,0 @@ -package org.scalajs.jsenv.phantomjs - -private[phantomjs] trait WebsocketManager { - def start(): Unit - def stop(): Unit - def sendMessage(msg: String): Unit - def localPort: Int - def isConnected: Boolean - def isClosed: Boolean -} diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala similarity index 88% rename from js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala rename to phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala index 0d5019fca..97a9b8c30 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala +++ b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/JettyWebsocketManager.scala @@ -1,3 +1,11 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ PhantomJS support for Scala.js ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2017, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ https://www.scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + package org.scalajs.jsenv.phantomjs import javax.servlet.http.HttpServletRequest diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala similarity index 98% rename from js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala rename to phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index 4aaf1a286..e9a54895d 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -1,11 +1,10 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - +/* __ *\ +** ________ ___ / / ___ __ ____ PhantomJS support for Scala.js ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2017, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ https://www.scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ package org.scalajs.jsenv.phantomjs diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala similarity index 81% rename from js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala rename to phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala index 428279e4c..6b9ab8962 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala +++ b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala @@ -1,3 +1,11 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ PhantomJS support for Scala.js ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2017, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ https://www.scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + package org.scalajs.jsenv.phantomjs import org.scalajs.core.tools.io.IO diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnv.scala similarity index 90% rename from js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala rename to phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnv.scala index 9c879332d..850b53512 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/RetryingComJSEnv.scala +++ b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnv.scala @@ -1,18 +1,19 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ +/* __ *\ +** ________ ___ / / ___ __ ____ PhantomJS support for Scala.js ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2017, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ https://www.scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ - -package org.scalajs.jsenv +package org.scalajs.jsenv.phantomjs import org.scalajs.core.tools.io._ import org.scalajs.core.tools.logging.Logger import org.scalajs.core.tools.jsdep.ResolvedJSDependency +import org.scalajs.jsenv._ + import scala.concurrent.{Future, Promise, ExecutionContext} import scala.concurrent.duration.Duration import scala.collection.mutable @@ -30,6 +31,10 @@ import scala.util.{Try, Failure, Success} * property. * * No retrying is performed for synchronous, or normal asynchronous runs. + * + * Although `RetryingComJSEnv` is agnostic of the underlying JS env, and is + * therefore not tied to PhantomJS, it is most often used to compensate for + * flakiness effects of PhantomJS. */ final class RetryingComJSEnv(val baseEnv: ComJSEnv, val maxRetries: Int) extends ComJSEnv { diff --git a/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketListener.scala b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketListener.scala new file mode 100644 index 000000000..a05f76407 --- /dev/null +++ b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketListener.scala @@ -0,0 +1,18 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ PhantomJS support for Scala.js ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2017, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ https://www.scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + +package org.scalajs.jsenv.phantomjs + +private[phantomjs] trait WebsocketListener { + def onRunning(): Unit + def onOpen(): Unit + def onClose(): Unit + def onMessage(msg: String): Unit + + def log(msg: String): Unit +} diff --git a/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketManager.scala b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketManager.scala new file mode 100644 index 000000000..489c4b420 --- /dev/null +++ b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/WebsocketManager.scala @@ -0,0 +1,18 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ PhantomJS support for Scala.js ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2017, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ https://www.scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + +package org.scalajs.jsenv.phantomjs + +private[phantomjs] trait WebsocketManager { + def start(): Unit + def stop(): Unit + def sendMessage(msg: String): Unit + def localPort: Int + def isConnected: Boolean + def isClosed: Boolean +} diff --git a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala b/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/PhantomJSTest.scala similarity index 59% rename from js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala rename to phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/PhantomJSTest.scala index b4fd37df0..c3894e102 100644 --- a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/PhantomJSTest.scala +++ b/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/PhantomJSTest.scala @@ -1,6 +1,6 @@ -package org.scalajs.jsenv.test +package org.scalajs.jsenv.phantomjs -import org.scalajs.jsenv.phantomjs.PhantomJSEnv +import org.scalajs.jsenv.test._ class PhantomJSTest extends JSEnvTest with ComTests { protected def newJSEnv: PhantomJSEnv = new PhantomJSEnv diff --git a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/PhantomJSWithCustomInitFilesTest.scala b/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/PhantomJSWithCustomInitFilesTest.scala similarity index 71% rename from js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/PhantomJSWithCustomInitFilesTest.scala rename to phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/PhantomJSWithCustomInitFilesTest.scala index 8c31699b4..17716457a 100644 --- a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/PhantomJSWithCustomInitFilesTest.scala +++ b/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/PhantomJSWithCustomInitFilesTest.scala @@ -1,6 +1,6 @@ -package org.scalajs.jsenv.test +package org.scalajs.jsenv.phantomjs -import org.scalajs.jsenv.phantomjs.PhantomJSEnv +import org.scalajs.jsenv.test._ class PhantomJSWithCustomInitFilesTest extends CustomInitFilesTest { protected def newJSEnv: PhantomJSEnv = new PhantomJSEnv { diff --git a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala b/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnvTest.scala similarity index 97% rename from js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala rename to phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnvTest.scala index 4aeccda56..484fb4d3b 100644 --- a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/RetryingComJSEnvTest.scala +++ b/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnvTest.scala @@ -1,10 +1,12 @@ -package org.scalajs.jsenv.test +package org.scalajs.jsenv.phantomjs import org.scalajs.core.tools.io.VirtualJSFile import org.scalajs.core.tools.jsdep.ResolvedJSDependency import org.scalajs.core.tools.logging._ + import org.scalajs.jsenv.nodejs.NodeJSEnv import org.scalajs.jsenv.{ComJSRunner, JSConsole, _} +import org.scalajs.jsenv.test._ import scala.concurrent.Future import scala.concurrent.duration.Duration diff --git a/phantomjs-sbt-plugin/src/main/scala/org/scalajs/jsenv/phantomjs/sbtplugin/PhantomJSEnvPlugin.scala b/phantomjs-sbt-plugin/src/main/scala/org/scalajs/jsenv/phantomjs/sbtplugin/PhantomJSEnvPlugin.scala new file mode 100644 index 000000000..363aab6d0 --- /dev/null +++ b/phantomjs-sbt-plugin/src/main/scala/org/scalajs/jsenv/phantomjs/sbtplugin/PhantomJSEnvPlugin.scala @@ -0,0 +1,102 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ PhantomJS support for Scala.js ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2017, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ https://www.scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + +package org.scalajs.jsenv.phantomjs.sbtplugin + +import sbt._ +import sbt.Keys._ + +import java.net.URLClassLoader + +import org.scalajs.sbtplugin.ScalaJSPlugin +import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ + +import org.scalajs.jsenv._ +import org.scalajs.jsenv.phantomjs._ + +/** An sbt plugin that simplifies the setup of [[PhantomJSEnv]]s. + * + * There is no need to use `enablePlugins(PhantomJSEnvPlugin)`, as this plugin + * is automatically triggered by Scala.js projects. + * + * Usually, one only needs to use the + * [[PhantomJSEnvPlugin.autoImport.PhantomJSEnv]] method. + */ +object PhantomJSEnvPlugin extends AutoPlugin { + override def requires: Plugins = ScalaJSPlugin + override def trigger: PluginTrigger = allRequirements + + object autoImport { + /** Class loader for PhantomJSEnv, used to load jetty8. + * + * Usually, you should not need to use `scalaJSPhantomJSClassLoader` + * directly. Instead, use the `PhantomJSEnv()` function. + */ + val scalaJSPhantomJSClassLoader: TaskKey[ClassLoader] = { + TaskKey[ClassLoader]( + "scalaJSPhantomJSClassLoader", + "Private class loader to load jetty8 without polluting the " + + "classpath. Only use this as the `jettyClassLoader` argument of " + + "a PhantomJSEnv.", + KeyRanks.Invisible) + } + + /** An [[sbt.Def.Initialize Def.Initialize]] for a [[PhantomJSEnv]]. + * + * Use this to specify in your build that you would like to run and/or + * test a project with PhantomJS: + * + * {{{ + * jsEnv := PhantomJSEnv().value + * }}} + * + * Note that the resulting [[sbt.Def.Setting Setting]] is not scoped at + * all, but must be scoped in a project that has the ScalaJSPlugin enabled + * to work properly. + * Therefore, either put the upper line in your project settings (common + * case) or scope it manually, using + * [[sbt.ProjectExtra.inScope[* Project.inScope]]. + */ + def PhantomJSEnv( + executable: String = "phantomjs", + args: Seq[String] = Seq.empty, + env: Map[String, String] = Map.empty, + autoExit: Boolean = true + ): Def.Initialize[Task[PhantomJSEnv]] = Def.task { + val loader = scalaJSPhantomJSClassLoader.value + new PhantomJSEnv(executable, args, env, autoExit, loader) + } + } + + import autoImport._ + + val phantomJSJettyModules: Seq[ModuleID] = Seq( + "org.eclipse.jetty" % "jetty-websocket" % "8.1.16.v20140903", + "org.eclipse.jetty" % "jetty-server" % "8.1.16.v20140903" + ) + + override def projectSettings: Seq[Setting[_]] = Seq( + /* Depend on jetty artifacts in a dummy configuration to be able to inject + * them into the PhantomJS runner if necessary. + * See scalaJSPhantomJSClassLoader. + */ + ivyConfigurations += config("phantom-js-jetty").hide, + libraryDependencies ++= phantomJSJettyModules.map(_ % "phantom-js-jetty"), + + scalaJSPhantomJSClassLoader := { + val report = update.value + val jars = report.select(configurationFilter("phantom-js-jetty")) + + val jettyLoader = + new URLClassLoader(jars.map(_.toURI.toURL).toArray, null) + + new PhantomJettyClassLoader(jettyLoader, getClass.getClassLoader) + } + ) + +} diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index dd51eb23d..cbd595b88 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -73,15 +73,8 @@ lazy val jetty9 = project.settings(baseSettings: _*). enablePlugins(ScalaJSPlugin). settings( name := "Scala.js sbt test with jetty9 on classpath", - // This project also tests packageJSDependencies, although we don't use it - jsDependencies ++= Seq( - RuntimeDOM, - // The jsDependenciesTest relies on this jQuery dependency - // If you change it, make sure we still test properly - "org.webjars" % "jquery" % "1.10.2" / "jquery.js" - ), // Use PhantomJS, allow cross domain requests - postLinkJSEnv := PhantomJSEnv(args = Seq("--web-security=no")).value, + jsEnv := PhantomJSEnv(args = Seq("--web-security=no")).value, Jetty9Test.runSetting ) @@ -163,6 +156,19 @@ lazy val multiTest = crossProject. lazy val multiTestJS = multiTest.js lazy val multiTestJVM = multiTest.jvm +lazy val jsDependenciesTestDependee = project. + settings(versionSettings: _*). + enablePlugins(ScalaJSPlugin). + settings( + // This project contains some jsDependencies to test in jsDependenciesTest + jsDependencies ++= Seq( + RuntimeDOM, + // The jsDependenciesTest relies on this jQuery dependency + // If you change it, make sure we still test properly + "org.webjars" % "jquery" % "1.10.2" / "jquery.js" + ) + ) + lazy val jsDependenciesTest = withRegretionTestForIssue2243( project.settings(versionSettings: _*). enablePlugins(ScalaJSPlugin). @@ -179,14 +185,17 @@ lazy val jsDependenciesTest = withRegretionTestForIssue2243( "org.webjars" % "mustachejs" % "0.8.2" / "mustache.js" commonJSName "Mustache", "org.webjars" % "mustachejs" % "0.8.2" / "0.8.2/mustache.js" commonJSName "Mustache", - // cause an ambiguity with jQuery dependency from jetty9 project (if we don't filter) + // cause an ambiguity with the jQuery dependency from the + // jsDependenciesTestDependee project (if we don't filter) ProvidedJS / "js/customJQuery/jquery.js" dependsOn "1.10.2/jquery.js", // Test minified dependencies "org.webjars" % "immutable" % "3.4.0" / "immutable.js" minified "immutable.min.js" ), - jsManifestFilter := ManifestFilters.reinterpretResourceNames("jetty9")( - "jquery.js" -> "1.10.2/jquery.js") + jsManifestFilter := { + ManifestFilters.reinterpretResourceNames("jsDependenciesTestDependee")( + "jquery.js" -> "1.10.2/jquery.js") + } ). settings(inConfig(Compile)(Seq( packageJSDependencies <<= packageJSDependencies.dependsOn(Def.task { @@ -227,7 +236,7 @@ lazy val jsDependenciesTest = withRegretionTestForIssue2243( streams.value.log.info("jsDependencies resolution test passed") }) )): _*). - dependsOn(jetty9) // depends on jQuery + dependsOn(jsDependenciesTestDependee) // depends on jQuery ) lazy val jsNoDependenciesTest = withRegretionTestForIssue2243( diff --git a/sbt-plugin-test/project/Jetty9Test.scala b/sbt-plugin-test/project/Jetty9Test.scala index 809b1bd94..e988c2754 100644 --- a/sbt-plugin-test/project/Jetty9Test.scala +++ b/sbt-plugin-test/project/Jetty9Test.scala @@ -27,17 +27,17 @@ object Jetty9Test { val code = new MemVirtualJSFile("runner.js").withContent( """ scalajsCom.init(function(msg) { - jQuery.ajax({ - url: msg, - success: function(dat) { - scalajsCom.send(dat.trim()); - scalajsCom.close(); - }, - error: function() { - scalajsCom.send("failed!"); - scalajsCom.close(); - } + var xhr = new XMLHttpRequest(); + xhr.open("GET", msg); + xhr.onload = (function() { + scalajsCom.send(xhr.responseText.trim()); + scalajsCom.close(); }); + xhr.onerror = (function() { + scalajsCom.send("failed!"); + scalajsCom.close(); + }); + xhr.send(); }); """ ) diff --git a/sbt-plugin-test/project/build.sbt b/sbt-plugin-test/project/build.sbt index fd8d93a70..79c666d22 100644 --- a/sbt-plugin-test/project/build.sbt +++ b/sbt-plugin-test/project/build.sbt @@ -1,4 +1,7 @@ addSbtPlugin("org.scala-js" % "sbt-scalajs" % org.scalajs.core.ir.ScalaJSVersions.current) +addSbtPlugin("org.scala-js" % "sbt-scalajs-env-phantomjs" % + org.scalajs.core.ir.ScalaJSVersions.current) + libraryDependencies += "org.eclipse.jetty" % "jetty-server" % "9.2.3.v20140905" From 73582a5ab72b43139fec6026e2b1119f45b7877d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 28 Mar 2017 16:51:41 +0200 Subject: [PATCH 76/84] Use VirtualJSFiles instead of ResolvedJSDependency in the JSEnv API. This removes the annoying dependency (sic!) of the `JSEnv` API on `ResolvedJSDependency`. This was the only part of `tools.jsdep` that was used in the `JSEnv` API, so it decouples those. This will be very important as we separate `jsDependencies` in a separate plugin, as the `tools.jsdep` package should go with that, out of the core. --- .../scalajs/jsenv/phantomjs/PhantomJSEnv.scala | 13 ++++++------- .../jsenv/phantomjs/RetryingComJSEnv.scala | 15 ++++----------- .../jsenv/phantomjs/RetryingComJSEnvTest.scala | 7 +++---- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index e9a54895d..0001dcfe8 100644 --- a/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -14,7 +14,6 @@ import org.scalajs.jsenv.Utils.OptDeadline import org.scalajs.core.ir.Utils.{escapeJS, fixFileURI} import org.scalajs.core.tools.io._ -import org.scalajs.core.tools.jsdep.ResolvedJSDependency import org.scalajs.core.tools.logging._ import java.io.{ Console => _, _ } @@ -40,30 +39,30 @@ class PhantomJSEnv( protected def vmName: String = "PhantomJS" protected def executable: String = phantomjsPath - override def jsRunner(libs: Seq[ResolvedJSDependency], + override def jsRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): JSRunner = { new PhantomRunner(libs, code) } - override def asyncRunner(libs: Seq[ResolvedJSDependency], + override def asyncRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): AsyncJSRunner = { new AsyncPhantomRunner(libs, code) } - override def comRunner(libs: Seq[ResolvedJSDependency], + override def comRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): ComJSRunner = { new ComPhantomRunner(libs, code) } - protected class PhantomRunner(libs: Seq[ResolvedJSDependency], + protected class PhantomRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile) extends ExtRunner(libs, code) with AbstractPhantomRunner - protected class AsyncPhantomRunner(libs: Seq[ResolvedJSDependency], + protected class AsyncPhantomRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile) extends AsyncExtRunner(libs, code) with AbstractPhantomRunner - protected class ComPhantomRunner(libs: Seq[ResolvedJSDependency], + protected class ComPhantomRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile) extends AsyncPhantomRunner(libs, code) with ComJSRunner { diff --git a/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnv.scala b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnv.scala index 850b53512..8aa373637 100644 --- a/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnv.scala +++ b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnv.scala @@ -10,7 +10,6 @@ package org.scalajs.jsenv.phantomjs import org.scalajs.core.tools.io._ import org.scalajs.core.tools.logging.Logger -import org.scalajs.core.tools.jsdep.ResolvedJSDependency import org.scalajs.jsenv._ @@ -43,27 +42,21 @@ final class RetryingComJSEnv(val baseEnv: ComJSEnv, def name: String = s"Retrying ${baseEnv.name}" - def jsRunner(libs: Seq[ResolvedJSDependency], - code: VirtualJSFile): JSRunner = { + def jsRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): JSRunner = baseEnv.jsRunner(libs, code) - } - def asyncRunner(libs: Seq[ResolvedJSDependency], - code: VirtualJSFile): AsyncJSRunner = { + def asyncRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): AsyncJSRunner = baseEnv.asyncRunner(libs, code) - } - def comRunner(libs: Seq[ResolvedJSDependency], - code: VirtualJSFile): ComJSRunner = { + def comRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): ComJSRunner = new RetryingComJSRunner(libs, code) - } /** Hack to work around abstract override in ComJSRunner */ private trait DummyJSRunner { def stop(): Unit = () } - private class RetryingComJSRunner(libs: Seq[ResolvedJSDependency], + private class RetryingComJSRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile) extends DummyJSRunner with ComJSRunner { private[this] val promise = Promise[Unit] diff --git a/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnvTest.scala b/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnvTest.scala index 484fb4d3b..0223510a7 100644 --- a/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnvTest.scala +++ b/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnvTest.scala @@ -1,7 +1,6 @@ package org.scalajs.jsenv.phantomjs import org.scalajs.core.tools.io.VirtualJSFile -import org.scalajs.core.tools.jsdep.ResolvedJSDependency import org.scalajs.core.tools.logging._ import org.scalajs.jsenv.nodejs.NodeJSEnv @@ -29,17 +28,17 @@ class RetryingComJSEnvTest extends JSEnvTest with ComTests { private[this] var fails = 0 private[this] var failedReceive = false - def jsRunner(libs: Seq[ResolvedJSDependency], + def jsRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): JSRunner = { baseEnv.jsRunner(libs, code) } - def asyncRunner(libs: Seq[ResolvedJSDependency], + def asyncRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): AsyncJSRunner = { baseEnv.asyncRunner(libs, code) } - def comRunner(libs: Seq[ResolvedJSDependency], + def comRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): ComJSRunner = { new FailingComJSRunner(baseEnv.comRunner(libs, code)) } From 6771f218294e40d56f6b5b6bd28b059a8c448e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 5 Apr 2017 14:54:05 +0200 Subject: [PATCH 77/84] Do not use jsDependencies (nor jQuery) in sbt-plugin-test/withDOM. This turns withDOM into a test for DOM support only, independent of `jsDependencies`. --- sbt-plugin-test/build.sbt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index cbd595b88..837e665eb 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -1,4 +1,5 @@ import org.scalajs.core.tools.jsdep.ManifestFilters +import org.scalajs.jsenv.nodejs.JSDOMNodeJSEnv name := "Scala.js sbt test" @@ -61,9 +62,7 @@ lazy val withDOM = project.settings(baseSettings: _*). enablePlugins(ScalaJSJUnitPlugin). settings( name := "Scala.js sbt test w/ DOM", - jsDependencies ++= Seq( - RuntimeDOM, - "org.webjars" % "jquery" % "1.10.2" / "jquery.js"), + jsEnv := new JSDOMNodeJSEnv(), scalaJSOutputWrapper := ( "// Scala.js - withDOM sbt test\n//\n// Compiled with Scala.js\n", "// End of Scala.js generated script") From bf2bd780bf20910a7d2ae7b84a2afd4e63960be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 8 Apr 2017 10:29:04 +0200 Subject: [PATCH 78/84] Unify the parameter names of external JS env constructors. They all standardize on `executable`, `args` en `env`, which are the names used by the `Initialize` constructors in `ScalaJSPlugin`. --- .../org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index 4aaf1a286..239aa4b37 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -29,17 +29,19 @@ import scala.concurrent.{ExecutionContext, TimeoutException, Future} import scala.concurrent.duration.Duration class PhantomJSEnv( - phantomjsPath: String = "phantomjs", - addArgs: Seq[String] = Seq.empty, - addEnv: Map[String, String] = Map.empty, + @deprecatedName('phantomjsPath) + protected val executable: String = "phantomjs", + @deprecatedName('addArgs) + args: Seq[String] = Seq.empty, + @deprecatedName('addEnv) + env: Map[String, String] = Map.empty, val autoExit: Boolean = true, jettyClassLoader: ClassLoader = null -) extends ExternalJSEnv(addArgs, addEnv) with ComJSEnv { +) extends ExternalJSEnv(args, env) with ComJSEnv { import PhantomJSEnv._ protected def vmName: String = "PhantomJS" - protected def executable: String = phantomjsPath override def jsRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile): JSRunner = { From 425390e450ed07849c872eb0e60ee7121273fe37 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 9 Apr 2017 19:27:26 +0200 Subject: [PATCH 79/84] Fix scala-js/scala-js#2883: Remove packageJSLauncher --- sbt-plugin-test/build.sbt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 837e665eb..f3830fa3f 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -50,7 +50,8 @@ lazy val noDOM = project.settings(baseSettings: _*). name := "Scala.js sbt test w/o DOM", scalaJSOutputWrapper := ( "// Scala.js - noDOM sbt test\n//\n// Compiled with Scala.js\n", - "// End of Scala.js generated script") + "// End of Scala.js generated script"), + scalaJSUseMainModuleInitializer := true ). /* This hopefully exposes concurrent uses of the linker. If it fails/gets * flaky, there is a bug somewhere - #2202 @@ -65,7 +66,8 @@ lazy val withDOM = project.settings(baseSettings: _*). jsEnv := new JSDOMNodeJSEnv(), scalaJSOutputWrapper := ( "// Scala.js - withDOM sbt test\n//\n// Compiled with Scala.js\n", - "// End of Scala.js generated script") + "// End of Scala.js generated script"), + scalaJSUseMainModuleInitializer := true ) lazy val jetty9 = project.settings(baseSettings: _*). From c189f1b612540508c35d8a03c5cb06780ab62e7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 10 Apr 2017 18:15:17 +0200 Subject: [PATCH 80/84] Fix scala-js/scala-js#2874: Remove the distinction libs/code in the JSEnv API. `JSEnv#jsRunner` and friends `asyncRunner` and `comRunner` now only take a `Seq[VirtualJSFiles]`, containing all JS files to be given to the JS environment. The JS environments therefore make no distinction between "library" files and the "code" file. The disinction was arbitrary anyway, and unifying them makes sure that all the JS files are first-class. In particular, it forced to solve shortcomings in terms of error handling in `JSDOMNodeJSEnv`. --- .../jsenv/phantomjs/PhantomJSEnv.scala | 51 +++++-------------- .../jsenv/phantomjs/RetryingComJSEnv.scala | 20 ++++---- .../phantomjs/RetryingComJSEnvTest.scala | 18 +++---- sbt-plugin-test/project/Jetty9Test.scala | 2 +- 4 files changed, 31 insertions(+), 60 deletions(-) diff --git a/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index 1b285a6b8..b4edeecbf 100644 --- a/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -41,32 +41,23 @@ class PhantomJSEnv( protected def vmName: String = "PhantomJS" - override def jsRunner(libs: Seq[VirtualJSFile], - code: VirtualJSFile): JSRunner = { - new PhantomRunner(libs, code) - } + override def jsRunner(files: Seq[VirtualJSFile]): JSRunner = + new PhantomRunner(files) - override def asyncRunner(libs: Seq[VirtualJSFile], - code: VirtualJSFile): AsyncJSRunner = { - new AsyncPhantomRunner(libs, code) - } + override def asyncRunner(files: Seq[VirtualJSFile]): AsyncJSRunner = + new AsyncPhantomRunner(files) - override def comRunner(libs: Seq[VirtualJSFile], - code: VirtualJSFile): ComJSRunner = { - new ComPhantomRunner(libs, code) - } + override def comRunner(files: Seq[VirtualJSFile]): ComJSRunner = + new ComPhantomRunner(files) - protected class PhantomRunner(libs: Seq[VirtualJSFile], - code: VirtualJSFile) extends ExtRunner(libs, code) - with AbstractPhantomRunner + protected class PhantomRunner(files: Seq[VirtualJSFile]) + extends ExtRunner(files) with AbstractPhantomRunner - protected class AsyncPhantomRunner(libs: Seq[VirtualJSFile], - code: VirtualJSFile) extends AsyncExtRunner(libs, code) - with AbstractPhantomRunner + protected class AsyncPhantomRunner(files: Seq[VirtualJSFile]) + extends AsyncExtRunner(files) with AbstractPhantomRunner - protected class ComPhantomRunner(libs: Seq[VirtualJSFile], - code: VirtualJSFile) extends AsyncPhantomRunner(libs, code) - with ComJSRunner { + protected class ComPhantomRunner(files: Seq[VirtualJSFile]) + extends AsyncPhantomRunner(files) with ComJSRunner { private var mgrIsRunning: Boolean = false @@ -403,9 +394,8 @@ class PhantomJSEnv( out.write(s""" Phantom.js Launcher """) - sendJS(getLibJSFiles(), out) - writeCodeLauncher(code, out) - out.write(s"\n\n\n") + sendJS(getJSFiles(), out) + out.write(s"\n\n\n") } protected def createTmpLauncherFile(): File = { @@ -486,17 +476,6 @@ class PhantomJSEnv( webTmpF } - - protected def writeCodeLauncher(code: VirtualJSFile, out: Writer): Unit = { - // Create a file with the launcher function. - val launcherFile = new MemVirtualJSFile("phantomjs-launcher.js") - launcherFile.content = s""" - // Phantom.js code launcher - // Origin: ${code.path} - function $launcherName() {${code.content}} - """ - writeJSFile(launcherFile, out) - } } protected def htmlEscape(str: String): String = str.flatMap { @@ -513,6 +492,4 @@ private object PhantomJSEnv { private final val MaxByteMessageSize = 32768 // 32 KB private final val MaxCharMessageSize = MaxByteMessageSize / 2 // 2B per char private final val MaxCharPayloadSize = MaxCharMessageSize - 1 // frag flag - - private final val launcherName = "scalaJSPhantomJSEnvLauncher" } diff --git a/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnv.scala b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnv.scala index 8aa373637..8da4e3fd0 100644 --- a/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnv.scala +++ b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnv.scala @@ -42,26 +42,26 @@ final class RetryingComJSEnv(val baseEnv: ComJSEnv, def name: String = s"Retrying ${baseEnv.name}" - def jsRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): JSRunner = - baseEnv.jsRunner(libs, code) + def jsRunner(files: Seq[VirtualJSFile]): JSRunner = + baseEnv.jsRunner(files) - def asyncRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): AsyncJSRunner = - baseEnv.asyncRunner(libs, code) + def asyncRunner(files: Seq[VirtualJSFile]): AsyncJSRunner = + baseEnv.asyncRunner(files) - def comRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): ComJSRunner = - new RetryingComJSRunner(libs, code) + def comRunner(files: Seq[VirtualJSFile]): ComJSRunner = + new RetryingComJSRunner(files) /** Hack to work around abstract override in ComJSRunner */ private trait DummyJSRunner { def stop(): Unit = () } - private class RetryingComJSRunner(libs: Seq[VirtualJSFile], - code: VirtualJSFile) extends DummyJSRunner with ComJSRunner { + private class RetryingComJSRunner(files: Seq[VirtualJSFile]) + extends DummyJSRunner with ComJSRunner { private[this] val promise = Promise[Unit] - private[this] var curRunner = baseEnv.comRunner(libs, code) + private[this] var curRunner = baseEnv.comRunner(files) private[this] var hasReceived = false private[this] var retryCount = 0 @@ -135,7 +135,7 @@ final class RetryingComJSEnv(val baseEnv: ComJSEnv, val oldRunner = curRunner curRunner = try { - baseEnv.comRunner(libs, code) + baseEnv.comRunner(files) } catch { case NonFatal(t) => _logger.error("Could not retry: creating an new runner failed: " + diff --git a/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnvTest.scala b/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnvTest.scala index 0223510a7..2161b6118 100644 --- a/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnvTest.scala +++ b/phantomjs-env/src/test/scala/org/scalajs/jsenv/phantomjs/RetryingComJSEnvTest.scala @@ -28,20 +28,14 @@ class RetryingComJSEnvTest extends JSEnvTest with ComTests { private[this] var fails = 0 private[this] var failedReceive = false - def jsRunner(libs: Seq[VirtualJSFile], - code: VirtualJSFile): JSRunner = { - baseEnv.jsRunner(libs, code) - } + def jsRunner(files: Seq[VirtualJSFile]): JSRunner = + baseEnv.jsRunner(files) - def asyncRunner(libs: Seq[VirtualJSFile], - code: VirtualJSFile): AsyncJSRunner = { - baseEnv.asyncRunner(libs, code) - } + def asyncRunner(files: Seq[VirtualJSFile]): AsyncJSRunner = + baseEnv.asyncRunner(files) - def comRunner(libs: Seq[VirtualJSFile], - code: VirtualJSFile): ComJSRunner = { - new FailingComJSRunner(baseEnv.comRunner(libs, code)) - } + def comRunner(files: Seq[VirtualJSFile]): ComJSRunner = + new FailingComJSRunner(baseEnv.comRunner(files)) /** Hack to work around abstract override in ComJSRunner */ private trait DummyJSRunner { diff --git a/sbt-plugin-test/project/Jetty9Test.scala b/sbt-plugin-test/project/Jetty9Test.scala index e988c2754..5a7dbdd38 100644 --- a/sbt-plugin-test/project/Jetty9Test.scala +++ b/sbt-plugin-test/project/Jetty9Test.scala @@ -42,7 +42,7 @@ object Jetty9Test { """ ) - val runner = jsEnv.comRunner(code) + val runner = jsEnv.comRunner(code :: Nil) runner.start(streams.value.log, jsConsole) From 5d52df87577cbf8a547b27ade126476f4fa438d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 13 Apr 2017 11:11:56 +0200 Subject: [PATCH 81/84] Restore the test for scala-js/scala-js#2202 (concurrent use of the linker). The previous test had basically been inhibited when we switched the default environment from Rhino to Node.js. Indeed, this caused `loadedJSEnv` not to use the linker itself, but rather depend on the result of `fastOptJS`. The test was therefore not testing anything anymore. We restore this test in a more explicit and robust way, with a custom task to concurrently use the linker of `fullOptJS`, and a dedicated test task to directly depend on the former + `fullOptJS`. We use `fullOptJS` instead of `fastOptJS` because it takes more time, increasing the likelihood of concurrent execution. --- sbt-plugin-test/build.sbt | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index f3830fa3f..ff83c8514 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -1,5 +1,11 @@ +import org.scalajs.core.tools.io._ import org.scalajs.core.tools.jsdep.ManifestFilters import org.scalajs.jsenv.nodejs.JSDOMNodeJSEnv +import org.scalajs.sbtplugin.ScalaJSPluginInternal._ +import org.scalajs.sbtplugin.Loggers.sbtLogger2ToolsLogger + +lazy val concurrentFakeFullOptJS = taskKey[Any]("") +lazy val concurrentUseOfLinkerTest = taskKey[Any]("") name := "Scala.js sbt test" @@ -56,7 +62,32 @@ lazy val noDOM = project.settings(baseSettings: _*). /* This hopefully exposes concurrent uses of the linker. If it fails/gets * flaky, there is a bug somewhere - #2202 */ - settings(inConfig(Compile)(run <<= run.dependsOn(fastOptJS, loadedJSEnv)): _*) + settings(inConfig(Compile)(Seq( + // A fake fullOptJS that we will run concurrently with the true fullOptJS + concurrentFakeFullOptJS := Def.taskDyn { + val s = (streams in fullOptJS).value + val log = s.log + val ir = (scalaJSIR in fullOptJS).value.data + val moduleInitializers = scalaJSModuleInitializers.value + + Def.task { + log.info("Fake full optimizing") + val linker = (scalaJSLinker in fullOptJS).value + linker.link(ir, moduleInitializers, + WritableMemVirtualJSFile("fake-fastopt.js"), + sbtLogger2ToolsLogger(log)) + }.tag((usesScalaJSLinkerTag in fullOptJS).value) + }.value, + + /* Depend on both fullOptJS and concurrentFakeFullOptJS, so that they + * are hopefully executed in parallel (potentially, but they should be + * blocked from actually doing so by the concurrent restrictions on + * usesScalaJSLinkerTag). + */ + concurrentUseOfLinkerTest := { + (fullOptJS.value, concurrentFakeFullOptJS.value) + } + ))) lazy val withDOM = project.settings(baseSettings: _*). enablePlugins(ScalaJSPlugin). From 62559a75ce105a52ec944e629c7d4577ca26c5be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 12 Apr 2017 22:27:03 +0200 Subject: [PATCH 82/84] Fix scala-js/scala-js#2875: Remove JSEnv.loadLibs, resolvedJSEnv and loadedJSEnv. We remove `resolvedJSEnv` simply by initializing `jsEnv` by default in the project scope, and using `jsEnv` instead of `resolvedJSEnv`. We remove `loadedJSEnv` by using instead the pair `jsEnv` + `jsExecutionFiles` explicitly. Finally, we can remove `JSEnv.loadLibs`, as it was only useful for `loadedJSEnv`. --- sbt-plugin-test/project/Jetty9Test.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sbt-plugin-test/project/Jetty9Test.scala b/sbt-plugin-test/project/Jetty9Test.scala index 5a7dbdd38..031bda154 100644 --- a/sbt-plugin-test/project/Jetty9Test.scala +++ b/sbt-plugin-test/project/Jetty9Test.scala @@ -21,7 +21,8 @@ object Jetty9Test { private val jettyPort = 23548 val runSetting = run <<= Def.inputTask { - val jsEnv = (loadedJSEnv in Compile).value.asInstanceOf[ComJSEnv] + val env = (jsEnv in Compile).value.asInstanceOf[ComJSEnv] + val files = (jsExecutionFiles in Compile).value val jsConsole = scalaJSConsole.value val code = new MemVirtualJSFile("runner.js").withContent( @@ -42,7 +43,7 @@ object Jetty9Test { """ ) - val runner = jsEnv.comRunner(code :: Nil) + val runner = env.comRunner(files :+ code) runner.start(streams.value.log, jsConsole) From 34ddcb65db6c5d649cd5b57839a82e5f222c0b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 24 Apr 2017 16:08:23 +0200 Subject: [PATCH 83/84] Upgrade to Scala 2.11.11. --- sbt-plugin-test/build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index dd51eb23d..ef8a62a4f 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -6,7 +6,7 @@ version := scalaJSVersion val versionSettings = Seq( version := scalaJSVersion, - scalaVersion := "2.11.8" + scalaVersion := "2.11.11" ) val baseSettings = versionSettings ++ Seq( From 686d8a3440775fc4563e4a1dd83dfb329366cb1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 18 May 2017 16:47:25 +0200 Subject: [PATCH 84/84] Import the PhantomJS env from the Scala.js core repository. This is the initial import of the PhantomJS env and its sbt plugin from the Scala.js core repository. The history of this commit reflects the entire history of relevant files from the core repo, filter-branch'ed to appear as if they had always been in this repo. This commit adds the specific setup of the build and tests. --- .travis.yml | 43 +++ build.sbt | 107 +++++++ .../phantomjs/PhantomJettyClassLoader.scala | 2 +- .../sbtplugin/PhantomJSEnvPlugin.scala | 2 +- project/build.properties | 1 + sbt-plugin-test/build.sbt | 277 +----------------- sbt-plugin-test/project/build.properties | 1 + sbt-plugin-test/project/build.sbt | 6 +- 8 files changed, 160 insertions(+), 279 deletions(-) create mode 100644 .travis.yml create mode 100644 build.sbt create mode 100644 project/build.properties create mode 100644 sbt-plugin-test/project/build.properties diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..4c8c397cf --- /dev/null +++ b/.travis.yml @@ -0,0 +1,43 @@ +sudo: false +language: scala +scala: + - 2.10.6 + - 2.11.11 + - 2.12.2 +jdk: + - oraclejdk8 +install: + - if [[ "${TRAVIS_SCALA_VERSION}" == "2.10.6" ]]; then TEST_SBT_PLUGIN=true; else TEST_SBT_PLUGIN=false; fi + # The default ivy resolution takes way too much time, and times out Travis builds. + # We use coursier instead. + - mkdir -p $HOME/.sbt/0.13/plugins/ + - echo 'addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC3")' > $HOME/.sbt/0.13/plugins/coursier.sbt + # While there is no published version of Scala.js 1.x, we have to locally build a snapshot. + - git clone https://github.com/scala-js/scala-js.git + - cd scala-js + - git checkout f21e7f5ea652fe4a186f1ecd8c0070fbb80edc80 + - sbt ++$TRAVIS_SCALA_VERSION ir/publishLocal tools/publishLocal jsEnvs/publishLocal jsEnvsTestKit/publishLocal + - | + if [[ "${TEST_SBT_PLUGIN}" == "true" ]]; then + sbt ++$TRAVIS_SCALA_VERSION testAdapter/publishLocal sbtPlugin/publishLocal && + sbt ++2.11.11 compiler/publishLocal library/publishLocal testInterface/publishLocal + fi + - cd .. +script: + - sbt ++$TRAVIS_SCALA_VERSION scalajs-phantomjs-env/test scalajs-phantomjs-env/doc + - | + if [[ "${TEST_SBT_PLUGIN}" == "true" ]]; then + sbt scalajs-phantomjs-env/publishLocal sbt-scalajs-env-phantomjs/publishLocal && \ + cd sbt-plugin-test && \ + sbt jetty9/run && \ + cd .. + fi +cache: + directories: + - $HOME/.ivy2/cache + - $HOME/.sbt + - $HOME/.coursier/cache +before_cache: + - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete + - find $HOME/.sbt -name "*.lock" -print -delete + - rm $HOME/.sbt/0.13/plugins/coursier.sbt diff --git a/build.sbt b/build.sbt new file mode 100644 index 000000000..c2480ea10 --- /dev/null +++ b/build.sbt @@ -0,0 +1,107 @@ +val scalaJSVersion = "1.0.0-SNAPSHOT" + +inThisBuild(Seq( + version := "0.1.0-SNAPSHOT", + organization := "org.scala-js", + + crossScalaVersions := Seq("2.10.6", "2.11.11", "2.12.2"), + scalaVersion := "2.10.6", + scalacOptions ++= Seq("-deprecation", "-feature", "-Xfatal-warnings"), + + homepage := Some(url("https://www.scala-js.org/")), + licenses += ("BSD New", + url("https://github.com/scala-js/scala-js-env-phantomjs/blob/master/LICENSE")), + scmInfo := Some(ScmInfo( + url("https://github.com/scala-js/scala-js-env-phantomjs"), + "scm:git:git@github.com:scala-js/scala-js-env-phantomjs.git", + Some("scm:git:git@github.com:scala-js/scala-js-env-phantomjs.git"))) +)) + +val commonSettings = Def.settings( + // Scaladoc linking + apiURL := { + val name = moduleName.value + val v = version.value + Some(url(s"https://www.scala-js.org/api/$name/$v/")) + }, + autoAPIMappings := true, + + publishMavenStyle := true, + publishTo := { + val nexus = "https://oss.sonatype.org/" + if (isSnapshot.value) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") + }, + pomExtra := ( + + + sjrd + Sébastien Doeraene + https://github.com/sjrd/ + + + gzm0 + Tobias Schlatter + https://github.com/gzm0/ + + + nicolasstucki + Nicolas Stucki + https://github.com/nicolasstucki/ + + + ), + pomIncludeRepository := { _ => false } +) + +lazy val root: Project = project.in(file(".")). + settings( + publishArtifact in Compile := false, + publish := {}, + publishLocal := {}, + + clean := clean.dependsOn( + clean in `scalajs-phantomjs-env`, + clean in `sbt-scalajs-env-phantomjs` + ).value + ) + +lazy val `scalajs-phantomjs-env`: Project = project.in(file("phantomjs-env")). + settings( + commonSettings, + + libraryDependencies ++= Seq( + "org.scala-js" %% "scalajs-js-envs" % scalaJSVersion, + "org.eclipse.jetty" % "jetty-websocket" % "8.1.16.v20140903" % "provided", + "org.eclipse.jetty" % "jetty-server" % "8.1.16.v20140903" % "provided", + + "com.novocode" % "junit-interface" % "0.11" % "test", + "org.scala-js" %% "scalajs-js-envs-test-kit" % scalaJSVersion % "test" + ) + ) + +lazy val `sbt-scalajs-env-phantomjs`: Project = project.in(file("phantomjs-sbt-plugin")). + settings( + commonSettings, + + sbtPlugin := true, + scalaBinaryVersion := + CrossVersion.binaryScalaVersion(scalaVersion.value), + + addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion), + + // Add API mappings for sbt (seems they don't export their API URL) + apiMappings ++= { + val deps = (externalDependencyClasspath in Compile).value + val sbtJars = deps filter { attributed => + val p = attributed.data.getPath + p.contains("/org.scala-sbt/") && p.endsWith(".jar") + } + val docUrl = + url(s"http://www.scala-sbt.org/${sbtVersion.value}/api/") + sbtJars.map(_.data -> docUrl).toMap + } + ). + dependsOn(`scalajs-phantomjs-env`) diff --git a/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala index 6b9ab8962..ea8f5ba93 100644 --- a/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala +++ b/phantomjs-env/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJettyClassLoader.scala @@ -10,7 +10,7 @@ package org.scalajs.jsenv.phantomjs import org.scalajs.core.tools.io.IO -/** A special [[java.lang.ClassLoader]] to load the Jetty 8 dependency of +/** A special `java.lang.ClassLoader` to load the Jetty 8 dependency of * [[PhantomJSEnv]] in a private space. * * It loads everything that belongs to `JettyWebsocketManager` itself (while diff --git a/phantomjs-sbt-plugin/src/main/scala/org/scalajs/jsenv/phantomjs/sbtplugin/PhantomJSEnvPlugin.scala b/phantomjs-sbt-plugin/src/main/scala/org/scalajs/jsenv/phantomjs/sbtplugin/PhantomJSEnvPlugin.scala index 363aab6d0..13b1a54b2 100644 --- a/phantomjs-sbt-plugin/src/main/scala/org/scalajs/jsenv/phantomjs/sbtplugin/PhantomJSEnvPlugin.scala +++ b/phantomjs-sbt-plugin/src/main/scala/org/scalajs/jsenv/phantomjs/sbtplugin/PhantomJSEnvPlugin.scala @@ -19,7 +19,7 @@ import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ import org.scalajs.jsenv._ import org.scalajs.jsenv.phantomjs._ -/** An sbt plugin that simplifies the setup of [[PhantomJSEnv]]s. +/** An sbt plugin that simplifies the setup of `PhantomJSEnv`s. * * There is no need to use `enablePlugins(PhantomJSEnvPlugin)`, as this plugin * is automatically triggered by Scala.js projects. diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 000000000..64317fdae --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.15 diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 774951173..efbf8a420 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -1,107 +1,11 @@ -import org.scalajs.core.tools.io._ -import org.scalajs.core.tools.jsdep.ManifestFilters -import org.scalajs.jsenv.nodejs.JSDOMNodeJSEnv -import org.scalajs.sbtplugin.ScalaJSPluginInternal._ -import org.scalajs.sbtplugin.Loggers.sbtLogger2ToolsLogger - -lazy val concurrentFakeFullOptJS = taskKey[Any]("") -lazy val concurrentUseOfLinkerTest = taskKey[Any]("") - -name := "Scala.js sbt test" - -version := scalaJSVersion - -val versionSettings = Seq( - version := scalaJSVersion, +inThisBuild(Seq( + version := "0.1.0-SNAPSHOT", scalaVersion := "2.11.11" -) - -val baseSettings = versionSettings ++ Seq( - testOptions += Tests.Argument( - TestFramework("com.novocode.junit.JUnitFramework"), "-v", "-a", "-s"), - - // Test that non-existent classpath entries are allowed - #2198 - fullClasspath in Compile += (baseDirectory in "root").value / - "non-existent-directory-please-dont-ever-create-this" -) - -val regressionTestForIssue2243 = TaskKey[Unit]("regressionTestForIssue2243", - "", KeyRanks.BTask) -val testScalaJSSourceMapAttribute = TaskKey[Unit]( - "testScalaJSSourceMapAttribute", "", KeyRanks.BTask) - -def withRegretionTestForIssue2243(project: Project): Project = { - project.settings(inConfig(Compile)(Seq( - regressionTestForIssue2243 := { - // Regression test for issue #2243 - val _ = Def.sequential(packageJSDependencies in Compile, - packageMinifiedJSDependencies in Compile).value - assert((artifactPath in(Compile, packageJSDependencies)).value.exists) - assert((artifactPath in(Compile, packageMinifiedJSDependencies)).value.exists) - streams.value.log.info("Regression test for issue #2243 passed") - } - )): _*) -} - -lazy val referencedCrossProjectJS = ProjectRef(file("referencedCrossProject"), "referencedCrossProjectJS") -lazy val referencedCrossProjectJVM = ProjectRef(file("referencedCrossProject"), "referencedCrossProjectJVM") - -lazy val root = project.in(file(".")). - aggregate(noDOM, withDOM, multiTestJS, multiTestJVM, referencedCrossProjectJS, referencedCrossProjectJVM) - -lazy val noDOM = project.settings(baseSettings: _*). - enablePlugins(ScalaJSPlugin). - enablePlugins(ScalaJSJUnitPlugin). - settings( - name := "Scala.js sbt test w/o DOM", - scalaJSOutputWrapper := ( - "// Scala.js - noDOM sbt test\n//\n// Compiled with Scala.js\n", - "// End of Scala.js generated script"), - scalaJSUseMainModuleInitializer := true - ). - /* This hopefully exposes concurrent uses of the linker. If it fails/gets - * flaky, there is a bug somewhere - #2202 - */ - settings(inConfig(Compile)(Seq( - // A fake fullOptJS that we will run concurrently with the true fullOptJS - concurrentFakeFullOptJS := Def.taskDyn { - val s = (streams in fullOptJS).value - val log = s.log - val ir = (scalaJSIR in fullOptJS).value.data - val moduleInitializers = scalaJSModuleInitializers.value - - Def.task { - log.info("Fake full optimizing") - val linker = (scalaJSLinker in fullOptJS).value - linker.link(ir, moduleInitializers, - WritableMemVirtualJSFile("fake-fastopt.js"), - sbtLogger2ToolsLogger(log)) - }.tag((usesScalaJSLinkerTag in fullOptJS).value) - }.value, - - /* Depend on both fullOptJS and concurrentFakeFullOptJS, so that they - * are hopefully executed in parallel (potentially, but they should be - * blocked from actually doing so by the concurrent restrictions on - * usesScalaJSLinkerTag). - */ - concurrentUseOfLinkerTest := { - (fullOptJS.value, concurrentFakeFullOptJS.value) - } - ))) +)) -lazy val withDOM = project.settings(baseSettings: _*). - enablePlugins(ScalaJSPlugin). - enablePlugins(ScalaJSJUnitPlugin). - settings( - name := "Scala.js sbt test w/ DOM", - jsEnv := new JSDOMNodeJSEnv(), - scalaJSOutputWrapper := ( - "// Scala.js - withDOM sbt test\n//\n// Compiled with Scala.js\n", - "// End of Scala.js generated script"), - scalaJSUseMainModuleInitializer := true - ) +name := "sbt-plugin-test" -lazy val jetty9 = project.settings(baseSettings: _*). +lazy val jetty9 = project. enablePlugins(ScalaJSPlugin). settings( name := "Scala.js sbt test with jetty9 on classpath", @@ -109,174 +13,3 @@ lazy val jetty9 = project.settings(baseSettings: _*). jsEnv := PhantomJSEnv(args = Seq("--web-security=no")).value, Jetty9Test.runSetting ) - -lazy val testFramework = crossProject.crossType(CrossType.Pure). - settings(versionSettings: _*). - settings(name := "Dummy cross JS/JVM test framework"). - jsSettings( - libraryDependencies += - "org.scala-js" %% "scalajs-test-interface" % scalaJSVersion - ). - jvmSettings( - libraryDependencies ++= Seq( - "org.scala-sbt" % "test-interface" % "1.0", - "org.scala-js" %% "scalajs-stubs" % scalaJSVersion % "provided" - ) - ) - -lazy val testFrameworkJS = testFramework.js -lazy val testFrameworkJVM = testFramework.jvm - -lazy val multiTest = crossProject. - jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin)). - settings( - testFrameworks ++= Seq( - TestFramework("sbttest.framework.DummyFramework"), - TestFramework("inexistent.Foo", "another.strange.Bar") - ) - ). - jsSettings(baseSettings: _*). - jsSettings( - name := "Multi test framework test JS", - // Make FrameworkDetector resilient to other output - #1572 - jsDependencies in Test += ProvidedJS / "consoleWriter.js", - - // Test isScalaJSProject (as a setting, it's evaluated when loading the build) - isScalaJSProject ~= { value => - assert(value, "isScalaJSProject should be true in multiTestJS") - value - }, - - // Test the scalaJSSourceMap attribute of fastOptJS and fullOptJS - testScalaJSSourceMapAttribute in Test := { - val fastOptFile = (fastOptJS in Test).value - assert(fastOptFile.get(scalaJSSourceMap).exists { - _.getPath == fastOptFile.data.getPath + ".map" - }, "fastOptJS does not have the correct scalaJSSourceMap attribute") - - val fullOptFile = (fullOptJS in Test).value - assert(fullOptFile.get(scalaJSSourceMap).exists { - _.getPath == fullOptFile.data.getPath + ".map" - }, "fullOptJS does not have the correct scalaJSSourceMap attribute") - } - ). - jvmSettings(versionSettings: _*). - jvmSettings( - name := "Multi test framework test JVM", - libraryDependencies += - "com.novocode" % "junit-interface" % "0.9" % "test", - - // Test isScalaJSProject (as a setting, it's evaluated when loading the build) - isScalaJSProject ~= { value => - assert(!value, "isScalaJSProject should be true in multiTestJVM") - value - } - ). - settings( - // Scala cross-version support for shared source directory - #2005 - unmanagedSourceDirectories in Compile := { - val srcDirs = (unmanagedSourceDirectories in Compile).value - val version = scalaBinaryVersion.value - val expected = - (baseDirectory.value.getParentFile / "shared" / "src" / "main" / s"scala-$version").getPath - assert(srcDirs.exists(_.getPath == expected)) - srcDirs - } - ). - dependsOn(testFramework % "test") - -lazy val multiTestJS = multiTest.js -lazy val multiTestJVM = multiTest.jvm - -lazy val jsDependenciesTestDependee = project. - settings(versionSettings: _*). - enablePlugins(ScalaJSPlugin). - settings( - // This project contains some jsDependencies to test in jsDependenciesTest - jsDependencies ++= Seq( - RuntimeDOM, - // The jsDependenciesTest relies on this jQuery dependency - // If you change it, make sure we still test properly - "org.webjars" % "jquery" % "1.10.2" / "jquery.js" - ) - ) - -lazy val jsDependenciesTest = withRegretionTestForIssue2243( - project.settings(versionSettings: _*). - enablePlugins(ScalaJSPlugin). - settings( - jsDependencies ++= Seq( - "org.webjars" % "historyjs" % "1.8.0" / "uncompressed/history.js", - ProvidedJS / "some-jquery-plugin.js" dependsOn "1.10.2/jquery.js", - ProvidedJS / "js/foo.js" dependsOn "uncompressed/history.js", - - // cause a circular dependency error if both "history.js"'s are considered equal - "org.webjars" % "historyjs" % "1.8.0" / "compressed/history.js" dependsOn "foo.js", - - // cause a duplicate commonJSName if the following are not considered equal - "org.webjars" % "mustachejs" % "0.8.2" / "mustache.js" commonJSName "Mustache", - "org.webjars" % "mustachejs" % "0.8.2" / "0.8.2/mustache.js" commonJSName "Mustache", - - // cause an ambiguity with the jQuery dependency from the - // jsDependenciesTestDependee project (if we don't filter) - ProvidedJS / "js/customJQuery/jquery.js" dependsOn "1.10.2/jquery.js", - - // Test minified dependencies - "org.webjars" % "immutable" % "3.4.0" / "immutable.js" minified "immutable.min.js" - ), - jsManifestFilter := { - ManifestFilters.reinterpretResourceNames("jsDependenciesTestDependee")( - "jquery.js" -> "1.10.2/jquery.js") - } - ). - settings(inConfig(Compile)(Seq( - packageJSDependencies <<= packageJSDependencies.dependsOn(Def.task { - // perform verifications on the ordering and deduplications - val resolvedDeps = resolvedJSDependencies.value.data - val relPaths = resolvedDeps.map(_.info.relPath) - - assert(relPaths.toSet == Set( - "META-INF/resources/webjars/mustachejs/0.8.2/mustache.js", - "META-INF/resources/webjars/historyjs/1.8.0/scripts/uncompressed/history.js", - "META-INF/resources/webjars/historyjs/1.8.0/scripts/compressed/history.js", - "META-INF/resources/webjars/jquery/1.10.2/jquery.js", - "META-INF/resources/webjars/immutable/3.4.0/immutable.js", - "js/foo.js", - "js/some-jquery-plugin.js", - "js/customJQuery/jquery.js"), - s"Bad set of relPathes: ${relPaths.toSet}") - - val minifiedRelPaths = resolvedDeps.flatMap(_.info.relPathMinified) - - assert(minifiedRelPaths.toSet == Set( - "META-INF/resources/webjars/immutable/3.4.0/immutable.min.js"), - s"Bad set of minifiedRelPathes: ${minifiedRelPaths.toSet}") - - val jQueryIndex = relPaths.indexWhere(_ endsWith "1.10.2/jquery.js") - val jQueryPluginIndex = relPaths.indexWhere(_ endsWith "/some-jquery-plugin.js") - assert(jQueryPluginIndex > jQueryIndex, - "the jQuery plugin appears before jQuery") - - val uncompressedHistoryIndex = relPaths.indexWhere(_ endsWith "/uncompressed/history.js") - val fooIndex = relPaths.indexWhere(_ endsWith "/foo.js") - val compressedHistoryIndex = relPaths.indexWhere(_ endsWith "/compressed/history.js") - assert(fooIndex > uncompressedHistoryIndex, - "foo.js appears before uncompressed/history.js") - assert(compressedHistoryIndex > fooIndex, - "compressed/history.js appears before foo.js") - - streams.value.log.info("jsDependencies resolution test passed") - }) - )): _*). - dependsOn(jsDependenciesTestDependee) // depends on jQuery -) - -lazy val jsNoDependenciesTest = withRegretionTestForIssue2243( - project.settings(versionSettings: _*). - enablePlugins(ScalaJSPlugin) -) - -// Test %%% macro - #1331 -val unusedSettings = Seq( - libraryDependencies += "org.example" %%% "dummy" % "0.1" -) diff --git a/sbt-plugin-test/project/build.properties b/sbt-plugin-test/project/build.properties new file mode 100644 index 000000000..64317fdae --- /dev/null +++ b/sbt-plugin-test/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.15 diff --git a/sbt-plugin-test/project/build.sbt b/sbt-plugin-test/project/build.sbt index 79c666d22..4b1df3f87 100644 --- a/sbt-plugin-test/project/build.sbt +++ b/sbt-plugin-test/project/build.sbt @@ -1,7 +1,3 @@ -addSbtPlugin("org.scala-js" % "sbt-scalajs" % - org.scalajs.core.ir.ScalaJSVersions.current) - -addSbtPlugin("org.scala-js" % "sbt-scalajs-env-phantomjs" % - org.scalajs.core.ir.ScalaJSVersions.current) +addSbtPlugin("org.scala-js" % "sbt-scalajs-env-phantomjs" % "0.1.0-SNAPSHOT") libraryDependencies += "org.eclipse.jetty" % "jetty-server" % "9.2.3.v20140905"