From 82c0ed199b2fe8c8c9f3d34ab94accf21b9fc397 Mon Sep 17 00:00:00 2001 From: EECOLOR Date: Sat, 14 Dec 2013 03:40:12 +0100 Subject: [PATCH] Integrate unit testing framework with sbt test discovery. - Tests can now be run using the test facilities built in sbt. To use it you need to add a dependency on a Scala.js test framework (for example Scala.js jasmine test framework) and add a marker interface (`scala.scalajs.test.Test`) to your test classes (or extend JasmineTest when using the Scala.js jasmine test framework). - The default Scala.js test framework and Scala.js test bridge class can be changed using sbt settings. - A reference implementation of the test framework using jasmine is provided. - Added experimental support for `env.js` in the run and test environment. Just add `env.js` webjar as a dependency to your project. - Run command now searches for objects that have a `main` method (normal sbt behavior). --- .travis.yml | 2 +- examples/testing/build.sbt | 0 .../src/main/scala/ElementCreator.scala | 7 + .../src/test/scala/ElementCreatorTest.scala | 26 ++ .../scala/org/scalajs/jasmine/Jasmine.scala | 13 + .../scalajs/jasmine}/JasmineExpectation.scala | 9 +- .../scala/org/scalajs/jasmine/Results.scala | 13 + .../main/scala/org/scalajs/jasmine/Spec.scala | 9 + .../org/scalajs/jasmine/SpecResults.scala | 8 + .../scala/org/scalajs/jasmine/Suite.scala | 8 + .../org/scalajs/jasmine/SuiteResults.scala | 9 + .../scala/scalajs/test/JasmineTest.scala | 8 +- .../scalajs/test/JasmineTestFramework.scala | 168 +++++++++++++ .../scala/scala/scalajs/test/EventProxy.scala | 25 ++ .../scalajs/test/ScriptStackElement.scala | 20 +- .../main/scala/scala/scalajs/test/Test.scala | 13 + .../scala/scala/scalajs/test/TestBridge.scala | 26 ++ .../scala/scalajs/test/TestFramework.scala | 14 ++ .../scala/scala/scalajs/test/TestOutput.scala | 32 +++ .../scala/scalajs/test/TestOutputBridge.scala | 85 +++++++ .../scala/scalajs/test/TestOutputLog.scala | 15 ++ project/ScalaJSBuild.scala | 82 +++--- .../scalajs/sbtplugin/RhinoBasedRun.scala | 233 ------------------ .../sbtplugin/ScalaJSEnvironment.scala | 17 ++ .../scalajs/sbtplugin/ScalaJSPlugin.scala | 112 ++++++--- .../sbtplugin/environment/Console.scala | 16 ++ .../sbtplugin/environment/LoggerConsole.scala | 20 ++ .../RhinoBasedScalaJSEnvironment.scala | 193 +++++++++++++++ .../environment/rhino/CodeBlock.scala | 17 ++ .../environment/rhino/LazyScalaJSScope.scala | 103 ++++++++ .../environment/rhino/ScalaJSCoreLib.scala | 60 +++++ .../environment/rhino/Utilities.scala | 78 ++++++ .../sbtplugin/environment/rhino/package.scala | 49 ++++ .../sourcemap/SourceMappedException.scala | 36 +++ .../sbtplugin/sourcemap/SourceMapper.scala | 65 +++++ .../sbtplugin/testing/EventProxy.scala | 94 +++++++ .../scalajs/sbtplugin/testing/Events.scala | 39 +++ .../sbtplugin/testing/TestFramework.scala | 34 +++ .../sbtplugin/testing/TestRunner.scala | 32 +++ .../scalajs/sbtplugin/testing/TestTask.scala | 52 ++++ test/src/test/resources/bootstrap.js | 25 -- .../test/resources/jasmine_rhino_reporter.js | 142 ----------- test/src/test/resources/main.js | 39 --- .../test/compiler/InteroperabilityTest.scala | 4 +- .../scalajs/test/compiler/LongTest.scala | 3 +- .../test/compiler/RegressionTest.scala | 4 +- .../scalajs/test/javalib/ArraysTest.scala | 5 +- .../scalajs/test/javalib/FormatterTest.scala | 8 +- .../scalajs/test/javalib/IntegerTest.scala | 4 +- .../scala/scalajs/test/javalib/LongTest.scala | 3 +- .../scalajs/test/javalib/ObjectTest.scala | 4 +- .../scalajs/test/javalib/StringTest.scala | 4 +- .../scalajs/test/javalib/ThrowablesTest.scala | 8 +- .../scalajs/test/jsinterop/ArrayTest.scala | 4 +- .../test/jsinterop/DictionaryTest.scala | 4 +- .../scalajs/test/jsinterop/DynamicTest.scala | 4 +- .../test/jsinterop/RuntimeLongTest.scala | 8 +- .../test/scalalib/EnumerationTest.scala | 4 +- 58 files changed, 1560 insertions(+), 559 deletions(-) create mode 100644 examples/testing/build.sbt create mode 100644 examples/testing/src/main/scala/ElementCreator.scala create mode 100644 examples/testing/src/test/scala/ElementCreatorTest.scala create mode 100644 jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Jasmine.scala rename {test/src/test/scala/scala/scalajs/test => jasmine-test-framework/src/main/scala/org/scalajs/jasmine}/JasmineExpectation.scala (52%) create mode 100644 jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Results.scala create mode 100644 jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Spec.scala create mode 100644 jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SpecResults.scala create mode 100644 jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Suite.scala create mode 100644 jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SuiteResults.scala rename test/src/test/scala/scala/scalajs/test/ScalaJSTest.scala => jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTest.scala (85%) create mode 100644 jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTestFramework.scala create mode 100644 library/src/main/scala/scala/scalajs/test/EventProxy.scala rename test/src/test/scala/scala/scalajs/test/Jasmine.scala => library/src/main/scala/scala/scalajs/test/ScriptStackElement.scala (50%) create mode 100644 library/src/main/scala/scala/scalajs/test/Test.scala create mode 100644 library/src/main/scala/scala/scalajs/test/TestBridge.scala create mode 100644 library/src/main/scala/scala/scalajs/test/TestFramework.scala create mode 100644 library/src/main/scala/scala/scalajs/test/TestOutput.scala create mode 100644 library/src/main/scala/scala/scalajs/test/TestOutputBridge.scala create mode 100644 library/src/main/scala/scala/scalajs/test/TestOutputLog.scala delete mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/RhinoBasedRun.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSEnvironment.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/Console.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/LoggerConsole.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/RhinoBasedScalaJSEnvironment.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/CodeBlock.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/LazyScalaJSScope.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/ScalaJSCoreLib.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/Utilities.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/package.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMappedException.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMapper.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/EventProxy.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/Events.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestFramework.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestRunner.scala create mode 100644 sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestTask.scala delete mode 100644 test/src/test/resources/bootstrap.js delete mode 100644 test/src/test/resources/jasmine_rhino_reporter.js delete mode 100644 test/src/test/resources/main.js diff --git a/.travis.yml b/.travis.yml index 922d59c971..6f7658c71e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: scala script: - - sbt package scalajs-test/test + - sbt package testing/test scalajs-test/test after_success: - if [[ "${PUBLISH_ENABLED}" == "true" && "${TRAVIS_PULL_REQUEST}" == "false" && "${TRAVIS_BRANCH}" == "master" && "${PUBLISH_USER}" != "" && "${PUBLISH_PASS}" != "" ]]; then sbt publish; fi scala: diff --git a/examples/testing/build.sbt b/examples/testing/build.sbt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/testing/src/main/scala/ElementCreator.scala b/examples/testing/src/main/scala/ElementCreator.scala new file mode 100644 index 0000000000..ccb3600592 --- /dev/null +++ b/examples/testing/src/main/scala/ElementCreator.scala @@ -0,0 +1,7 @@ +import scala.scalajs.js.Dynamic.global + +object ElementCreator { + val jQ = global.jQuery + + def create() = jQ("body").append(jQ("

Test

")) +} diff --git a/examples/testing/src/test/scala/ElementCreatorTest.scala b/examples/testing/src/test/scala/ElementCreatorTest.scala new file mode 100644 index 0000000000..d513fc67c8 --- /dev/null +++ b/examples/testing/src/test/scala/ElementCreatorTest.scala @@ -0,0 +1,26 @@ +import scala.scalajs.js +import scala.scalajs.js.Dynamic.global +import scala.scalajs.test.JasmineTest + +object ElementCreatorTest extends JasmineTest { + + // ElementCreator expects jquery to be present + global.importScripts("jquery.js") + + describe("ElementCreator") { + + it("should be able to create an element in the body") { + // create the element + ElementCreator.create() + + // jquery would make this easier, but I wanted to + // only use pure html in the test itself + val body = global.document.getElementsByTagName("body") + .asInstanceOf[js.Array[js.Dynamic]].head + + // the Scala.js DOM API would make this easier + expect(body.firstChild.tagName.toString == "H1").toBeTruthy + expect(body.firstChild.innerHTML.toString == "Test").toBeTruthy + } + } +} diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Jasmine.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Jasmine.scala new file mode 100644 index 0000000000..b215835eb6 --- /dev/null +++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Jasmine.scala @@ -0,0 +1,13 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +object Jasmine extends js.GlobalScope { + def describe(name: String, suite: js.Function0[_]): Unit = ??? + def it(title: String, test: js.Function0[_]): Unit = ??? + def xdescribe(name: String, suite: js.Function0[_]): Unit = ??? + def xit(title: String, test: js.Function0[_]): Unit = ??? + def beforeEach(block: js.Function0[_]): Unit = ??? + def afterEach(block: js.Function0[_]): Unit = ??? + def expect(exp: js.Any): JasmineExpectation = ??? +} diff --git a/test/src/test/scala/scala/scalajs/test/JasmineExpectation.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/JasmineExpectation.scala similarity index 52% rename from test/src/test/scala/scala/scalajs/test/JasmineExpectation.scala rename to jasmine-test-framework/src/main/scala/org/scalajs/jasmine/JasmineExpectation.scala index 6789c79038..a4f7d268d8 100644 --- a/test/src/test/scala/scala/scalajs/test/JasmineExpectation.scala +++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/JasmineExpectation.scala @@ -1,11 +1,4 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js Test Suite ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ -package scala.scalajs.test +package org.scalajs.jasmine import scala.scalajs.js import java.util.regex.Pattern diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Results.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Results.scala new file mode 100644 index 0000000000..1053123a60 --- /dev/null +++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Results.scala @@ -0,0 +1,13 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +trait Result extends js.Object { + def `type`: js.String = ??? + val trace: js.Dynamic = ??? +} + +trait ExpectationResult extends Result { + def passed(): js.Boolean = ??? + val message: js.String = ??? +} diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Spec.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Spec.scala new file mode 100644 index 0000000000..47d284aefb --- /dev/null +++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Spec.scala @@ -0,0 +1,9 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +trait Spec extends js.Object { + def results(): SpecResults = ??? + val description: js.String = ??? + val suite: Suite = ??? +} diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SpecResults.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SpecResults.scala new file mode 100644 index 0000000000..0a74c8b5b2 --- /dev/null +++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SpecResults.scala @@ -0,0 +1,8 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +trait SpecResults extends js.Object { + def passed(): js.Boolean = ??? + def getItems(): js.Array[Result] = ??? +} diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Suite.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Suite.scala new file mode 100644 index 0000000000..2d5ad73a3e --- /dev/null +++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/Suite.scala @@ -0,0 +1,8 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +trait Suite extends js.Object { + def results(): SuiteResults = ??? + val description: js.String = ??? +} diff --git a/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SuiteResults.scala b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SuiteResults.scala new file mode 100644 index 0000000000..789de9c00d --- /dev/null +++ b/jasmine-test-framework/src/main/scala/org/scalajs/jasmine/SuiteResults.scala @@ -0,0 +1,9 @@ +package org.scalajs.jasmine + +import scala.scalajs.js + +trait SuiteResults extends js.Object { + val passedCount: js.Number = ??? + val failedCount: js.Number = ??? + val totalCount: js.Number = ??? +} diff --git a/test/src/test/scala/scala/scalajs/test/ScalaJSTest.scala b/jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTest.scala similarity index 85% rename from test/src/test/scala/scala/scalajs/test/ScalaJSTest.scala rename to jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTest.scala index 841a90ef63..184f935f24 100644 --- a/test/src/test/scala/scala/scalajs/test/ScalaJSTest.scala +++ b/jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTest.scala @@ -1,16 +1,20 @@ /* __ *\ -** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** ________ ___ / / ___ __ ____ Scala.js Test Framework ** ** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** ** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** ** /____/\___/_/ |_/____/_/ | |__/ /____/ ** ** |/____/ ** \* */ + + package scala.scalajs.test import scala.scalajs.js import java.util.regex.Pattern +import org.scalajs.jasmine.Jasmine +import org.scalajs.jasmine.JasmineExpectation -class ScalaJSTest { +class JasmineTest extends Test { def describe(name: String)(suite: => Unit): Unit = Jasmine.describe(name, suite _) def it(title: String)(test: => Unit): Unit = Jasmine.it(title, test _) def xdescribe(name: String)(suite: => Unit): Unit = Jasmine.xdescribe(name, suite _) diff --git a/jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTestFramework.scala b/jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTestFramework.scala new file mode 100644 index 0000000000..7ff9c6ad20 --- /dev/null +++ b/jasmine-test-framework/src/main/scala/scala/scalajs/test/JasmineTestFramework.scala @@ -0,0 +1,168 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Framework ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.test + +import scala.scalajs.js +import scala.scalajs.js.Dynamic.global +import scala.scalajs.js.JavaScriptException + +import org.scalajs.jasmine.ExpectationResult +import org.scalajs.jasmine.Result +import org.scalajs.jasmine.Spec +import org.scalajs.jasmine.Suite + +object JasmineTestFramework extends TestFramework { + + /* Stub-out timer methods used by Jasmine and not provided by Rhino. */ + if (!global.setTimeout) { + global.setTimeout = scalaJSStub("setTimeout") + global.clearTimeout = scalaJSStub("clearTimeout") + global.setInterval = scalaJSStub("setInterval") + global.clearInterval = scalaJSStub("clearInterval") + } + + def scalaJSStub(name: String): js.Function = { () => + global.console.log("Stub for " + name + " called") + } + + // make sure jasmine is loaded + global.importScripts("jasmine.js") + + def runTests(testOutput: TestOutput)(tests: => Unit): Unit = { + val jasmine = global.jasmine + val reporter = new JasmineTestFramework(testOutput) + + try { + tests + + val jasmineEnv = jasmine.getEnv() + jasmineEnv.addReporter(reporter.asInstanceOf[js.Any]) + jasmineEnv.updateInterval = 0 + jasmineEnv.execute() + } catch { + case JavaScriptException(exception) => + val stack = exception.asInstanceOf[js.Dynamic].stack + testOutput.error("Problem executing code in tests: " + exception, + reporter.getScriptStack(stack)) + } + } +} + +class JasmineTestFramework(testOutput: TestOutput) { + private var currentSuite: Suite = _ + + def reportRunnerStarting(): Unit = { + testOutput.log.info("") + } + + def reportSpecStarting(spec: Spec): Unit = { + if (currentSuite != spec.suite) { + currentSuite = spec.suite + info(currentSuite.description) + } + } + + def reportSpecResults(spec: Spec): Unit = { + val results = spec.results() + val description = spec.description + + if (results.passed) { + testOutput.succeeded(s" $success $description") + } else { + error(s" $failure $description") + + results.getItems foreach displayResult + } + } + + def reportSuiteResults(suite: Suite): Unit = { + var results = suite.results() + + info("") + val title = "Total for suite " + suite.description + val message = + s"${results.totalCount} specs, ${results.failedCount} failure" + + if (results.passedCount != results.totalCount) { + error(title) + errorWithInfoColor(message) + } else { + info(title) + infoWithInfoColor(message) + } + info("") + } + + def reportRunnerResults(): Unit = { + // no need to report + } + + private def info(str: String) = + testOutput.log.info(str) + + private def infoWithInfoColor(str: String) = + info(withColor(testOutput.infoColor, str)) + + private def errorWithInfoColor(str: String) = + error(withColor(testOutput.infoColor, str)) + + private def error(msg: js.Any) = + testOutput.log.error(msg.toString) + + private def withColor(color: String, message: String) = + testOutput.color(message, color) + + private def sanitizeMessage(message: String) = { + val FilePattern = """^(.+?) [^ ]+\.js \(line \d+\)\.*?$""".r + val EvalPattern = """^(.+?) in eval.+\(eval\).+?\(line \d+\).*?$""".r + + message match { + case FilePattern(originalMessage) => originalMessage + case EvalPattern(originalMessage) => originalMessage + case message => message + } + } + + private def failure = withColor(testOutput.errorColor, "x") + private def success = withColor(testOutput.successColor, "+") + + private def displayResult(result: Result) = { + (result.`type`: String) match { + case "log" => + info(s" ${result.toString}") + case "expect" => + val r = result.asInstanceOf[ExpectationResult] + if (!r.passed()) { + val message = sanitizeMessage(r.message) + val stack = getScriptStack(r.trace.stack) + + if (stack.isEmpty) testOutput.failure(s" $message") + else testOutput.error(s" $message", stack) + } + } + } + + private def getScriptStack(stack: js.Any): Array[ScriptStackElement] = { + if (stack.isInstanceOf[js.String]) { + val StackTracePattern = """^(.+?) ([^ ]+\.js):(\d+).*?$""".r + val stackString: String = stack.asInstanceOf[js.String] + + stackString + .split("\n") + .map { + case StackTracePattern(originalMessage, fileName, lineNumber) => + ScriptStackElement(fileName, "", lineNumber.toInt) + case unknown => + throw JavaScriptException("Unknown stack element: " + unknown) + } + .takeWhile(e => !(e.fileName contains "jasmine.js")) + } else Array.empty + } +} diff --git a/library/src/main/scala/scala/scalajs/test/EventProxy.scala b/library/src/main/scala/scala/scalajs/test/EventProxy.scala new file mode 100644 index 0000000000..b3f1889c4f --- /dev/null +++ b/library/src/main/scala/scala/scalajs/test/EventProxy.scala @@ -0,0 +1,25 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.test + +import scala.scalajs.js + +trait EventProxy extends js.Object { + def error(message: js.String, stack: js.Array[js.Dictionary]): Unit = ??? + def failure(message: js.String, stack: js.Array[js.Dictionary]): Unit = ??? + def succeeded(message: js.String): Unit = ??? + def skipped(message: js.String): Unit = ??? + def pending(message: js.String): Unit = ??? + def ignored(message: js.String): Unit = ??? + def canceled(message: js.String): Unit = ??? + + def error(message: js.String): Unit = ??? + def info(message: js.String): Unit = ??? +} diff --git a/test/src/test/scala/scala/scalajs/test/Jasmine.scala b/library/src/main/scala/scala/scalajs/test/ScriptStackElement.scala similarity index 50% rename from test/src/test/scala/scala/scalajs/test/Jasmine.scala rename to library/src/main/scala/scala/scalajs/test/ScriptStackElement.scala index c6460d6ed7..d9159a5afb 100644 --- a/test/src/test/scala/scala/scalajs/test/Jasmine.scala +++ b/library/src/main/scala/scala/scalajs/test/ScriptStackElement.scala @@ -1,20 +1,22 @@ /* __ *\ -** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** ________ ___ / / ___ __ ____ Scala.js API ** ** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** ** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** ** /____/\___/_/ |_/____/_/ | |__/ /____/ ** ** |/____/ ** \* */ + + package scala.scalajs.test import scala.scalajs.js -object Jasmine extends js.GlobalScope { - def describe(name: String, suite: js.Function0[_]): Unit = ??? - def it(title: String, test: js.Function0[_]): Unit = ??? - def xdescribe(name: String, suite: js.Function0[_]): Unit = ??? - def xit(title: String, test: js.Function0[_]): Unit = ??? - def beforeEach(block: js.Function0[_]): Unit = ??? - def afterEach(block: js.Function0[_]): Unit = ??? - def expect(exp: js.Any): JasmineExpectation = ??? +class ScriptStackElement( + val fileName: String, + val functionName: String, + val lineNumber: Int) + +object ScriptStackElement { + def apply(fileName: String, functionName: String, lineNumber: Int) = + new ScriptStackElement(fileName, functionName, lineNumber) } diff --git a/library/src/main/scala/scala/scalajs/test/Test.scala b/library/src/main/scala/scala/scalajs/test/Test.scala new file mode 100644 index 0000000000..5b05086c53 --- /dev/null +++ b/library/src/main/scala/scala/scalajs/test/Test.scala @@ -0,0 +1,13 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.test + +/** Marker trait for Scala.js tests */ +trait Test diff --git a/library/src/main/scala/scala/scalajs/test/TestBridge.scala b/library/src/main/scala/scala/scalajs/test/TestBridge.scala new file mode 100644 index 0000000000..9f20f8f392 --- /dev/null +++ b/library/src/main/scala/scala/scalajs/test/TestBridge.scala @@ -0,0 +1,26 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.test + +import scala.scalajs.js +import js.Dynamic.{ newInstance, global } +import js.JavaScriptException + +class TestBridge(reporter: EventProxy, framework: String, test: String) { + val testOutput = new TestOutputBridge(reporter) + + val testFramework = + global.ScalaJS.modules.applyDynamic(framework)() + .asInstanceOf[TestFramework] + + testFramework.runTests(testOutput) { + global.ScalaJS.modules.applyDynamic(test)() + } +} diff --git a/library/src/main/scala/scala/scalajs/test/TestFramework.scala b/library/src/main/scala/scala/scalajs/test/TestFramework.scala new file mode 100644 index 0000000000..9fb59460ac --- /dev/null +++ b/library/src/main/scala/scala/scalajs/test/TestFramework.scala @@ -0,0 +1,14 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.test + +trait TestFramework { + def runTests(testOutput: TestOutput)(tests: => Unit): Unit +} diff --git a/library/src/main/scala/scala/scalajs/test/TestOutput.scala b/library/src/main/scala/scala/scalajs/test/TestOutput.scala new file mode 100644 index 0000000000..152ebb846f --- /dev/null +++ b/library/src/main/scala/scala/scalajs/test/TestOutput.scala @@ -0,0 +1,32 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.test + +trait TestOutput { + val errorColor: String + val successColor: String + val infoColor: String + + def color(message: String, color: String): String + + def error(message: String, stack: Array[ScriptStackElement]): Unit + def error(message: String): Unit + def failure(message: String, stack: Array[ScriptStackElement]): Unit + def failure(message: String): Unit + def succeeded(message: String): Unit + def skipped(message: String): Unit + def pending(message: String): Unit + def ignored(message: String): Unit + def canceled(message: String): Unit + + def log: TestOutputLog + + def getCurrentStack(): Array[ScriptStackElement] +} diff --git a/library/src/main/scala/scala/scalajs/test/TestOutputBridge.scala b/library/src/main/scala/scala/scalajs/test/TestOutputBridge.scala new file mode 100644 index 0000000000..13c0ab132e --- /dev/null +++ b/library/src/main/scala/scala/scalajs/test/TestOutputBridge.scala @@ -0,0 +1,85 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.test + +import scala.scalajs.js +import js.Dynamic.{ newInstance, global } +import js.JavaScriptException + +class TestOutputBridge(eventProxy: EventProxy) extends TestOutput { + + val errorColor = "\u001b[31m" + val successColor = "\u001b[32m" + val infoColor = "\u001b[34m" + val reset = "\u001b[0m" + + def color(message: String, color: String): String = + message.split('\n').mkString(color, reset + '\n' + color, reset) + + def error(message: String, stack: Array[ScriptStackElement]): Unit = + eventProxy.error(message, toJsArray(stack)) + + def error(message: String): Unit = + error(message, Array.empty) + + def failure(message: String, stack: Array[ScriptStackElement]): Unit = + eventProxy.failure(message, toJsArray(stack)) + + def failure(message: String): Unit = + failure(message, Array.empty) + + def succeeded(message: String): Unit = + eventProxy.succeeded(message) + + def skipped(message: String): Unit = + eventProxy.skipped(message) + + def pending(message: String): Unit = + eventProxy.pending(message) + + def ignored(message: String): Unit = + eventProxy.ignored(message) + + def canceled(message: String): Unit = + eventProxy.canceled(message) + + val log = + new TestOutputLog { + def info(message: String): Unit = eventProxy.info(message) + def error(message: String): Unit = eventProxy.error(message) + } + + def getCurrentStack(): Array[ScriptStackElement] = { + val RhinoExceptionClass = global.Packages.org.mozilla.javascript.JavaScriptException + val rhinoException = newInstance(RhinoExceptionClass)("stack creation") + val rhinoStack = rhinoException.getScriptStack().asInstanceOf[js.Array[js.Dynamic]] + val stack = + for (i <- 0 until rhinoStack.length.asInstanceOf[js.Number].toInt) yield { + val e = rhinoStack(i) + ScriptStackElement(e.fileName.toString, e.functionName.toString, + e.lineNumber.asInstanceOf[js.Number].toInt) + } + + stack.toArray + } + + private def toJsArray(a: Array[ScriptStackElement]): js.Array[js.Dictionary] = + elementToDictionary(a) + + private def elementToDictionary(a: Seq[ScriptStackElement]): Array[js.Dictionary] = + (for (el <- a) yield stackElementToDictionary(el)).toArray + + private def stackElementToDictionary(s: ScriptStackElement): js.Dictionary = { + js.Dictionary( + "fileName" -> (s.fileName: js.String), + "functionName" -> (s.functionName: js.String), + "lineNumber" -> (s.lineNumber: js.Number)) + } +} diff --git a/library/src/main/scala/scala/scalajs/test/TestOutputLog.scala b/library/src/main/scala/scala/scalajs/test/TestOutputLog.scala new file mode 100644 index 0000000000..fb0e06ed8c --- /dev/null +++ b/library/src/main/scala/scala/scalajs/test/TestOutputLog.scala @@ -0,0 +1,15 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.test + +trait TestOutputLog { + def info(message: String): Unit + def error(message: String): Unit +} diff --git a/project/ScalaJSBuild.scala b/project/ScalaJSBuild.scala index f563c8fa1e..11d112cc5c 100644 --- a/project/ScalaJSBuild.scala +++ b/project/ScalaJSBuild.scala @@ -62,16 +62,17 @@ object ScalaJSBuild extends Build { publishArtifact in Compile := false, clean := clean.dependsOn( - // compiler, library and sbt-plugin are aggregated + // compiler, plugin, library and jasmineTestFramework are aggregated clean in corejslib, clean in javalib, clean in scalalib, clean in libraryAux, clean in test, clean in examples, - clean in exampleHelloWorld, clean in exampleReversi).value, + clean in exampleHelloWorld, clean in exampleReversi, + clean in exampleTesting).value, publish := {}, publishLocal := {} ) ).aggregate( - compiler, plugin, library + compiler, plugin, library, jasmineTestFramework ) lazy val compiler: Project = Project( @@ -223,6 +224,20 @@ object ScalaJSBuild extends Build { )) ).dependsOn(compiler % "plugin") + // Test framework + + lazy val jasmineTestFramework = Project( + id = "scalajs-jasmine-test-framework", + base = file("jasmine-test-framework"), + settings = defaultSettings ++ myScalaJSSettings ++ Seq( + name := "Scala.js jasmine test framework", + + libraryDependencies ++= Seq( + "org.webjars" % "jasmine" % "1.3.1" + ) + ) + ).dependsOn(compiler % "plugin", library) + // Utils /* Dirty trick to add our Scala.js library on the classpath without adding a @@ -230,12 +245,23 @@ object ScalaJSBuild extends Build { * time we make a change in the compiler, and we want to test it on an * example or with the test suite. */ - def useLibraryButDoNotDependOnIt(config: Configuration) = ( + def useProjectButDoNotDependOnIt(project: Project, config: Configuration) = ( unmanagedClasspath in config += { - val libraryJar = (artifactPath in (library, Compile, packageBin)).value + val libraryJar = (artifactPath in (project, Compile, packageBin)).value Attributed.blank(libraryJar) }) + def useLibraryButDoNotDependOnIt = Seq( + useProjectButDoNotDependOnIt(library, Compile), + useProjectButDoNotDependOnIt(library, Test) + ) + + def useJasmineTestFrameworkButDoNotDependOnIt = Seq( + useProjectButDoNotDependOnIt(jasmineTestFramework, Test), + libraryDependencies ++= + (libraryDependencies in jasmineTestFramework).value map (_ % "test") + ) + // Examples lazy val examples: Project = Project( @@ -246,9 +272,10 @@ object ScalaJSBuild extends Build { ) ).aggregate(exampleHelloWorld, exampleReversi) - lazy val exampleSettings = defaultSettings ++ myScalaJSSettings ++ Seq( - useLibraryButDoNotDependOnIt(Compile), - + lazy val exampleSettings = defaultSettings ++ myScalaJSSettings ++ ( + useLibraryButDoNotDependOnIt ++ + useJasmineTestFrameworkButDoNotDependOnIt + ) ++ Seq( // Add the startup.js file of this example project unmanagedSources in (Compile, packageJS) += baseDirectory.value / "startup.js" @@ -272,32 +299,31 @@ object ScalaJSBuild extends Build { ) ).dependsOn(compiler % "plugin") - // Testing + lazy val exampleTesting = Project( + id = "testing", + base = file("examples") / "testing", + settings = exampleSettings ++ Seq( + name := "Testing - Scala.js example", + moduleName := "testing", - val jasmineVersion = "1.3.1" + libraryDependencies ++= Seq( + "org.webjars" % "jquery" % "1.10.2" % "test", + "org.webjars" % "envjs" % "1.2" % "test" + ) + ) + ).dependsOn(compiler % "plugin") + + // Testing lazy val test: Project = Project( id = "scalajs-test", base = file("test"), - settings = defaultSettings ++ myScalaJSSettings ++ Seq( + settings = defaultSettings ++ myScalaJSSettings ++ ( + useLibraryButDoNotDependOnIt ++ + useJasmineTestFrameworkButDoNotDependOnIt + ) ++ Seq( name := "Scala.js test suite", - publishArtifact in Compile := false, - useLibraryButDoNotDependOnIt(Test), - - libraryDependencies += "org.webjars" % "jasmine" % jasmineVersion % "test", - - // We don't need the HTML reporter, since we use the Rhino reporter - sources in (Test, packageExternalDepsJS) ~= { srcs => - srcs.filterNot(_.name == "jasmine-html.js") - }, - - // And a hack to make sure bootstrap.js is included before jasmine.js - sources in (Test, Keys.test) ~= { srcs => - val bootstrap: File = srcs.find(_.name == "bootstrap.js").get - val (before, after) = - srcs.filterNot(_ == bootstrap).span(_.name != "jasmine.js") - before ++ Seq(bootstrap) ++ after - } + publishArtifact in Compile := false ) ).dependsOn(compiler % "plugin") diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/RhinoBasedRun.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/RhinoBasedRun.scala deleted file mode 100644 index d13a5737b9..0000000000 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/RhinoBasedRun.scala +++ /dev/null @@ -1,233 +0,0 @@ -/* Scala.js sbt plugin - * Copyright 2013 LAMP/EPFL - * @author Sébastien Doeraene - */ - -package scala.scalajs.sbtplugin - -import scala.language.implicitConversions - -import sbt._ - -import scala.collection.mutable - -import org.mozilla.javascript._ - -object RhinoBasedRun { - - private class ContextOps(val self: Context) { - def evaluateFile(scope: Scriptable, file: File, - securityDomain: AnyRef = null): Any = { - val reader = new java.io.FileReader(file) - try { - self.evaluateReader(scope, reader, - file.getAbsolutePath, 1, securityDomain) - } finally { - reader.close() - } - } - } - - private implicit def context2ops(ctx: Context): ContextOps = - new ContextOps(ctx) - - /** A proxy for a ScalaJS "scope" field that loads scripts lazily - * - * E.g., ScalaJS.c, which is a scope with the Scala.js classes, can be - * turned to a LazyScalaJSScope. Upon first access to a field of ScalaJS.c, - * say ScalaJS.c.scala_Option, the script defining that particular - * field will be loaded. - * This is possible because the relative path to the script can be derived - * from the name of the property being accessed. - * - * It is immensely useful, because it allows to load lazily only the scripts - * that are actually needed. - */ - private class LazyScalaJSScope( - providers: scala.collection.Map[String, File], - globalScope: Scriptable, - base: Scriptable, - isModule: Boolean = false, - isTraitImpl: Boolean = false) - extends Scriptable { - - private val fields = mutable.HashMap.empty[String, Any] - private var prototype: Scriptable = _ - private var parentScope: Scriptable = _ - - { - // Pre-fill fields with the properties of `base` - for (id <- base.getIds()) { - (id.asInstanceOf[Any]: @unchecked) match { - case name: String => put(name, this, base.get(name, base)) - case index: Int => put(index, this, base.get(index, base)) - } - } - } - - private def load(name: String): Unit = { - val relativeFileName = nameToRelativeFileName(name) - providers.get(relativeFileName) foreach { file => - val ctx = Context.getCurrentContext() - ctx.evaluateFile(globalScope, file) - } - } - - private def nameToRelativeFileName(name: String): String = { - val name1 = if (isTraitImpl) name.split("__")(0) else name - val name2 = name1.replace("_", "/").replace("$und", "_") - if (isModule) name2 + "$.js" - else name2 + ".js" - } - - override def getClassName() = "LazyScalaJSScope" - - override def get(name: String, start: Scriptable) = { - fields.getOrElse(name, { - load(name) - fields.getOrElse(name, Scriptable.NOT_FOUND) - }).asInstanceOf[AnyRef] - } - override def get(index: Int, start: Scriptable) = - get(index.toString, start) - - override def has(name: String, start: Scriptable) = - fields.contains(name) - override def has(index: Int, start: Scriptable) = - has(index.toString, start) - - override def put(name: String, start: Scriptable, value: Any) = { - fields(name) = value - } - override def put(index: Int, start: Scriptable, value: Any) = - put(index.toString, start, value) - - override def delete(name: String) = () - override def delete(index: Int) = () - - override def getPrototype() = prototype - override def setPrototype(value: Scriptable) = prototype = value - - override def getParentScope() = parentScope - override def setParentScope(value: Scriptable) = parentScope = value - - override def getIds() = fields.keys.toArray - - override def getDefaultValue(hint: java.lang.Class[_]) = { - base.getDefaultValue(hint) - } - - override def hasInstance(instance: Scriptable) = false - } - - /** Run a sequence of JavaScript scripts, with a Scala.js flavor - * If given the original Scala.js classpath, it can hijack the Scala.js - * scripts in the sequence to load them lazily, the first time they are - * required. - */ - def scalaJSRunJavaScript(inputs: Seq[File], trace: (=> Throwable) => Unit, - console: Option[AnyRef], - useLazyScalaJSScopes: Boolean = false, - scalaJSClasspath: Seq[File] = Nil, - mainClass: Option[String] = None, - arguments: Array[String] = Array[String]()): Unit = { - val ctx = Context.enter() - try { - val scope = ctx.initStandardObjects() - - console foreach { consoleObject => - ScriptableObject.putProperty(scope, "console", - Context.javaToJS(consoleObject, scope)) - } - - try { - if (!useLazyScalaJSScopes || - !inputs.exists(_.getName == "scalajs-corejslib.js")) { - // Easy, just evaluate all input files, in order - for (input <- inputs) - ctx.evaluateFile(scope, input) - } else { - // The smart thing that hijacks ScalaJS-related things - runJavaScriptWithLazyScalaJSScopes(ctx, scope, inputs, scalaJSClasspath) - } - - mainClass foreach (runMainClass(ctx, scope, _, arguments)) - } catch { - case e: Exception => - trace(e) // print the stack trace while we're in the Context - throw new RuntimeException("Exception while running JS code", e) - } - } finally { - Context.exit() - } - } - - private def runJavaScriptWithLazyScalaJSScopes(ctx: Context, - scope: Scriptable, inputs: Seq[File], - scalaJSClasspath: Seq[File]): Unit = { - - val providers = mutable.HashMap.empty[String, File] - val ScalaJSProvider = """[0-9]{4}-(.*\.js)""".r - - for (input <- inputs) { - input.getName match { - case "scalajs-corejslib.js" => - ctx.evaluateFile(scope, input) - - // Hijack the global ScalaJS instance - val ScalaJS = scope.get("ScalaJS", scope).asInstanceOf[Scriptable] - - def patchField(name: String, isModule: Boolean = false, - isTraitImpl: Boolean = false): Unit = { - val base = ScalaJS.get(name, ScalaJS).asInstanceOf[Scriptable] - val lazyfied = new LazyScalaJSScope(providers, scope, base, - isModule, isTraitImpl) - ScalaJS.put(name, ScalaJS, lazyfied) - } - - patchField("data") - patchField("c") - patchField("inheritable") - patchField("classes") - patchField("impls", isTraitImpl = true) - patchField("moduleInstances", isModule = true) - patchField("modules", isModule = true) - patchField("is") - patchField("as") - patchField("isArrayOf") - patchField("asArrayOf") - - case ScalaJSProvider(fileName) => - val relative = scalaJSClasspath collectFirst { - case base if IO.relativize(base, input).isDefined => - IO.relativize(base, input).get - } - - if (relative.isDefined) { - val rel = relative.get.replace("\\", "/") - val lastSlash = rel.lastIndexOf("/") - val relativeFileName = rel.substring(0, lastSlash+1) + fileName - providers += (relativeFileName -> input) - } else { - // Oops! We found a Scala.js file that is not in the classpath! - ctx.evaluateFile(scope, input) - } - - case _ => - ctx.evaluateFile(scope, input) - } - } - } - - private def runMainClass(ctx: Context, scope: Scriptable, - mainClass: String, arguments: Array[String]): Unit = { - - val moduleFieldName = mainClass.replace("_", "$und").replace(".", "_") - - val script = s"""ScalaJS.modules.$moduleFieldName().main__AT__V( - ScalaJS.makeNativeArrayWrapper( - ScalaJS.data.java_lang_String.getArrayOf(), []))""" - - ctx.evaluateString(scope, script, "", 1, null) - } -} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSEnvironment.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSEnvironment.scala new file mode 100644 index 0000000000..da53c4241c --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSEnvironment.scala @@ -0,0 +1,17 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin + +import org.mozilla.javascript.Context +import org.mozilla.javascript.ScriptableObject + +trait ScalaJSEnvironment { + def runInContextAndScope(code: (Context, ScriptableObject) => Unit): Unit +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPlugin.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPlugin.scala index c21262bdea..c3cbd777e8 100644 --- a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPlugin.scala +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPlugin.scala @@ -1,18 +1,21 @@ -/* Scala.js sbt plugin - * Copyright 2013 LAMP/EPFL - * @author Sébastien Doeraene - */ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + package scala.scalajs.sbtplugin import sbt._ -import inc.{ IncOptions, ClassfileManager } +import sbt.inc.{ IncOptions, ClassfileManager } import Keys._ import scala.collection.mutable import SourceMapCat.catJSFilesAndTheirSourceMaps -import RhinoBasedRun._ import com.google.javascript.jscomp.{ SourceFile => ClosureSource, @@ -20,9 +23,12 @@ import com.google.javascript.jscomp.{ CompilerOptions => ClosureOptions, _ } -import scala.collection.JavaConversions._ +import scala.collection.JavaConverters._ + +import environment.{Console, LoggerConsole, RhinoBasedScalaJSEnvironment} +import environment.rhino.{CodeBlock, Utilities} -import org.mozilla.{ javascript => rhino } +import scala.scalajs.sbtplugin.testing.TestFramework object ScalaJSPlugin extends Plugin { val scalaJSVersion = "0.2-SNAPSHOT" @@ -44,6 +50,16 @@ object ScalaJSPlugin extends Plugin { "Pretty-print the output of optimizeJS") val optimizeJSExterns = taskKey[Seq[File]]( "Extern files to use with optimizeJS") + + val loggingConsole = taskKey[Option[Console]]( + "The logging console used by the Scala.js jvm environment") + val prepareEnvironment = taskKey[ScalaJSEnvironment]( + "Prepares a jvm environment in where Scala.js files can be run and tested") + + val scalaJSTestBridgeClass = settingKey[String]( + "The Scala.js class that delegates test calls to the given test framework") + val scalaJSTestFramework = settingKey[String]( + "The Scala.js class that is used as a test framework, for example a class that wraps Jasmine") } import ScalaJSKeys._ @@ -88,14 +104,6 @@ object ScalaJSPlugin extends Plugin { var global = {}; """ - /** A proxy for a Logger that looks like a Mozilla console object */ - private class LoggingConsole(logger: Logger) { - def log(x: Any): Unit = logger.info(x.toString) - def info(x: Any): Unit = logger.info(x.toString) - def warn(x: Any): Unit = logger.warn(x.toString) - def error(x: Any): Unit = logger.error(x.toString) - } - val scalaJSExternalCompileConfigSettings: Seq[Setting[_]] = inTask(compile)( Defaults.runnerTask ) ++ Seq( @@ -426,7 +434,7 @@ object ScalaJSPlugin extends Plugin { val compiler = new ClosureCompiler val result = compiler.compile( - closureExterns, closureSources, options) + closureExterns.asJava, closureSources.asJava, options) val errors = result.errors.toList val warnings = result.warnings.toList @@ -457,15 +465,6 @@ object ScalaJSPlugin extends Plugin { } ) - def scalaJSRunJavaScriptTask(streams: TaskKey[TaskStreams], - sources: TaskKey[Seq[File]], classpath: TaskKey[Classpath]) = Def.task { - val s = streams.value - s.log.info("Running ...") - val console = new LoggingConsole(s.log) - scalaJSRunJavaScript(sources.value, s.log.trace, Some(console), true, - classpath.value.map(_.data)) - } - def scalaJSRunInputsSettings(scoped: Scoped) = Seq( sources in scoped := ( (sources in packageExternalDepsJS).value ++ @@ -480,22 +479,65 @@ object ScalaJSPlugin extends Plugin { ) ) + def createScalaJSEnvironment(scoped: Scoped): Def.Initialize[Task[ScalaJSEnvironment]] = + Def.task { + val inputs = (sources in scoped).value + val classpath = (fullClasspath in scoped).value.map(_.data) + val logger = streams.value.log + val console = loggingConsole.value + + new RhinoBasedScalaJSEnvironment(inputs, classpath, console, logger.trace) + } + val scalaJSRunSettings = scalaJSRunInputsSettings(run) ++ Seq( - run := { - scalaJSRunJavaScriptTask(streams, sources in run, - fullClasspath in run).value + prepareEnvironment <<= createScalaJSEnvironment(run), + + run <<= { + import Def.parserToInput + val parser = Def.spaceDelimited() + + Def.inputTask { + val mainClassName = (mainClass in run).value.getOrElse( + sys.error("No main class detected.")) + val args = parser.parsed + + prepareEnvironment.value.runInContextAndScope { (context, scope) => + new CodeBlock(context, scope) with Utilities { + val mainModule = getModule(mainClassName) + callMethod(mainModule, "main__AT__V", null) // TODO use args + } + } + } } ) val scalaJSCompileSettings = scalaJSConfigSettings ++ scalaJSRunSettings - val scalaJSTestSettings = scalaJSConfigSettings ++ scalaJSRunInputsSettings(test) ++ Seq( - test := { - scalaJSRunJavaScriptTask(streams, sources in test, - fullClasspath in test).value + val scalaJSTestFrameworkSettings = Seq( + scalaJSTestFramework := "scala.scalajs.test.JasmineTestFramework", + scalaJSTestBridgeClass := "scala.scalajs.test.TestBridge", + prepareEnvironment <<= createScalaJSEnvironment(test), + + loadedTestFrameworks := { + loadedTestFrameworks.value.updated( + sbt.TestFramework(classOf[TestFramework].getName), + new TestFramework( + environment = prepareEnvironment.value, + testRunnerClass = scalaJSTestBridgeClass.value, + testFramework = scalaJSTestFramework.value) + ) } ) + val scalaJSTestSettings = ( + scalaJSConfigSettings ++ + scalaJSRunInputsSettings(test) ++ + scalaJSTestFrameworkSettings + ) + + def defaultLoggingConsole = + loggingConsole := Some(new LoggerConsole(streams.value.log)) + val scalaJSDefaultConfigs = ( inConfig(Compile)(scalaJSCompileSettings) ++ inConfig(Test)(scalaJSTestSettings) @@ -505,7 +547,9 @@ object ScalaJSPlugin extends Plugin { excludeDefaultScalaLibrary := false, optimizeJSPrettyPrint := false, - optimizeJSExterns := Seq() + optimizeJSExterns := Seq(), + + defaultLoggingConsole ) val scalaJSAbstractSettings: Seq[Setting[_]] = ( diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/Console.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/Console.scala new file mode 100644 index 0000000000..7493f18f30 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/Console.scala @@ -0,0 +1,16 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.environment + +import sbt.Logger + +trait Console { + def log(msg: Any): Unit +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/LoggerConsole.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/LoggerConsole.scala new file mode 100644 index 0000000000..98fa69ecd9 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/LoggerConsole.scala @@ -0,0 +1,20 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.environment + +import sbt.Logger + +/** A proxy for a Logger that looks like a Mozilla console object */ +class LoggerConsole(logger: Logger) extends Console { + def log(msg: Any): Unit = logger.info(msg.toString) + def info(msg: Any): Unit = logger.info(msg.toString) + def warn(msg: Any): Unit = logger.warn(msg.toString) + def error(msg: Any): Unit = logger.error(msg.toString) +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/RhinoBasedScalaJSEnvironment.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/RhinoBasedScalaJSEnvironment.scala new file mode 100644 index 0000000000..77508a7017 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/RhinoBasedScalaJSEnvironment.scala @@ -0,0 +1,193 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.environment + +import java.io.File + +import scala.collection.Map +import scala.collection.mutable +import scala.scalajs.sbtplugin.ScalaJSEnvironment +import scala.scalajs.sbtplugin.sourcemap.SourceMappedException +import scala.util.matching.Regex + +import org.mozilla.javascript.Context +import org.mozilla.javascript.RhinoException +import org.mozilla.javascript.Scriptable +import org.mozilla.javascript.ScriptableObject + +import rhino.ContextOps +import rhino.ScalaJSCoreLib +import rhino.ScriptableObjectOps + +import sbt.IO + +class RhinoBasedScalaJSEnvironment( + inputs: Seq[File], classpath: Seq[File], + console: Option[Console], + trace: (=> Throwable) => Unit) extends ScalaJSEnvironment { + + val ScalaJSProviderPattern = """[0-9]{4}-(.*\.js)""".r + + /* Splits the inputs into 4 categories + * + * 1. The core lib (using lazy loading for Scala.js files on the classpath) + * 2. Other js files and + * 3. Scala.js files (not on the classpath) + * 4. Env.js lib if it is present + */ + lazy val (scalaJSCoreLib, availableJsFiles, scalaJSFiles, envJSLib) = { + var scalaJSCoreLib: Option[ScalaJSCoreLib] = None + val scalaJSProviders, availableJsFiles = mutable.Map.empty[String, File] + val scalaJSFiles = mutable.Seq.empty[File] + var envJSLib: Option[File] = None + + inputs.foreach { + case ScalaJSCore(lib) => + scalaJSCoreLib = Some(lib.withProviders(scalaJSProviders)) + + case file @ RelativeScalaJSProvider(relativeFileName) => + scalaJSProviders += relativeFileName -> file + + case file @ ScalaJSProvider() => + scalaJSFiles :+ file + + case file @ EnvJSLib() => + envJSLib = Some(file) + + case file => + availableJsFiles += file.getName -> file + } + + if (scalaJSCoreLib == None && (scalaJSProviders.nonEmpty | scalaJSFiles.nonEmpty)) + throw new RuntimeException("Could not find scalajs-corejslib.js, make sure it's on the classpath") + + (scalaJSCoreLib, availableJsFiles, scalaJSFiles, envJSLib) + } + + /** Executes code in an environment where the Scala.js library is set up to + * load its classes lazily. + * + * Other js files can be loaded using the importScripts javascript + * function. + */ + def runInContextAndScope(code: (Context, ScriptableObject) => Unit): Unit = { + val context = Context.enter() + try { + val scope = context.initStandardObjects() + + envJSLib.foreach { file => + context.setOptimizationLevel(-1) + //please do not print the envjs header + scope.addFunction("print", args => ()) + context.evaluateFile(scope, file) + //add the real print function + scope.addFunction("print", print(console)) + } + + console.foreach { console => + ScriptableObject.putProperty(scope, "console", + Context.javaToJS(console, scope)) + } + + scope.addFunction("importScripts", importScripts(context, scope, availableJsFiles)) + + try { + // We only need to make the core lib available + scalaJSCoreLib.foreach(_.insertInto(context, scope)) + + // Any Scala.js files that were not on the classpath need to be loaded + scalaJSFiles.foreach(context.evaluateFile(scope, _)) + + code(context, scope) + } catch { + case e: RhinoException => + trace(e) // print the stack trace while we're in the Context + throw new SourceMappedException(e) + + case e: Exception => + trace(e) // print the stack trace while we're in the Context + throw new RuntimeException("Exception while running JS code", e) + } + } finally { + Context.exit() + } + } + + private def print(console: Option[Console])(args: Array[AnyRef]) = + console.foreach(_.log(args.mkString(" "))) + + private def importScripts(context: Context, globalScope: Scriptable, + availableJsFiles: Map[String, File])(args: Array[AnyRef]) = { + val urls = args.map(_.toString) + + val files = + for { + url <- urls + possibleName = url.split("/").lastOption + name <- possibleName + possibleFile = availableJsFiles.get(name) + if possibleFile.forall(_.getAbsolutePath endsWith name) + } yield url -> possibleFile + + files.foreach { + case (_, Some(file)) => + context.evaluateFile(globalScope, file) + case (url, None) => + throw new RuntimeException("Could not find file: '" + url + + "', make sure you make it available on any classpath") + } + } + + private class FileNameMatcher(pattern: Regex) { + def unapply(file: File): Boolean = { + file.getName match { + case pattern(_*) => true + case _ => false + } + } + } + + private val EnvJSLib = new FileNameMatcher("""env\.rhino\.(?:[\d\.]+\.)?js""".r) + private val ScalaJSProvider = new FileNameMatcher(ScalaJSProviderPattern) + + private object ScalaJSCore { + def unapply(file: File) = + if (file.getName == "scalajs-corejslib.js") + Some(new ScalaJSCoreLib(file)) + else None + } + + private object RelativeScalaJSProvider { + def unapply(file: File): Option[String] = { + val classpath = classpathOf(file) + + (file.getName, classpath) match { + case (ScalaJSProviderPattern(fileName), Some(classpath)) => + Some(makeRelative(fileName, classpath)) + case _ => None + } + } + + private def makeRelative(fileName: String, classpath: String) = { + val rel = classpath.replace("\\", "/") + val lastSlash = rel.lastIndexOf("/") + val relativeFileName = rel.substring(0, lastSlash + 1) + fileName + relativeFileName + } + + private def classpathOf(file: File) = { + classpath.view + .map(IO.relativize(_, file)) + .collectFirst { + case Some(path) => path + } + } + } +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/CodeBlock.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/CodeBlock.scala new file mode 100644 index 0000000000..9c8544d386 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/CodeBlock.scala @@ -0,0 +1,17 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.environment.rhino + +import org.mozilla.javascript.Scriptable +import org.mozilla.javascript.Context + +abstract class CodeBlock( + protected val context: Context, + protected val scope: Scriptable) diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/LazyScalaJSScope.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/LazyScalaJSScope.scala new file mode 100644 index 0000000000..b85e61b5d3 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/LazyScalaJSScope.scala @@ -0,0 +1,103 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.environment.rhino + +import org.mozilla.javascript.Scriptable +import java.io.File +import org.mozilla.javascript.Context +import scala.collection.mutable + +/** A proxy for a ScalaJS "scope" field that loads scripts lazily + * + * E.g., ScalaJS.c, which is a scope with the Scala.js classes, can be + * turned to a LazyScalaJSScope. Upon first access to a field of ScalaJS.c, + * say ScalaJS.c.scala_Option, the script defining that particular + * field will be loaded. + * This is possible because the relative path to the script can be derived + * from the name of the property being accessed. + * + * It is immensely useful, because it allows to load lazily only the scripts + * that are actually needed. + */ +class LazyScalaJSScope( + providers: scala.collection.Map[String, File], + globalScope: Scriptable, + base: Scriptable, + isModule: Boolean = false, + isTraitImpl: Boolean = false) extends Scriptable { + + private val fields = mutable.HashMap.empty[String, Any] + private var prototype: Scriptable = _ + private var parentScope: Scriptable = _ + + { + // Pre-fill fields with the properties of `base` + for (id <- base.getIds()) { + (id.asInstanceOf[Any]: @unchecked) match { + case name: String => put(name, this, base.get(name, base)) + case index: Int => put(index, this, base.get(index, base)) + } + } + } + + private def load(name: String): Unit = { + val relativeFileName = nameToRelativeFileName(name) + providers.get(relativeFileName) foreach { file => + val ctx = Context.getCurrentContext() + ctx.evaluateFile(globalScope, file) + } + } + + private def nameToRelativeFileName(name: String): String = { + val name1 = if (isTraitImpl) name.split("__")(0) else name + val name2 = name1.replace("_", "/").replace("$und", "_") + if (isModule) name2 + "$.js" + else name2 + ".js" + } + + override def getClassName() = "LazyScalaJSScope" + + override def get(name: String, start: Scriptable) = { + fields.getOrElse(name, { + load(name) + fields.getOrElse(name, Scriptable.NOT_FOUND) + }).asInstanceOf[AnyRef] + } + override def get(index: Int, start: Scriptable) = + get(index.toString, start) + + override def has(name: String, start: Scriptable) = + fields.contains(name) + override def has(index: Int, start: Scriptable) = + has(index.toString, start) + + override def put(name: String, start: Scriptable, value: Any) = { + fields(name) = value + } + override def put(index: Int, start: Scriptable, value: Any) = + put(index.toString, start, value) + + override def delete(name: String) = () + override def delete(index: Int) = () + + override def getPrototype() = prototype + override def setPrototype(value: Scriptable) = prototype = value + + override def getParentScope() = parentScope + override def setParentScope(value: Scriptable) = parentScope = value + + override def getIds() = fields.keys.toArray + + override def getDefaultValue(hint: java.lang.Class[_]) = { + base.getDefaultValue(hint) + } + + override def hasInstance(instance: Scriptable) = false +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/ScalaJSCoreLib.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/ScalaJSCoreLib.scala new file mode 100644 index 0000000000..f86738b01f --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/ScalaJSCoreLib.scala @@ -0,0 +1,60 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.environment.rhino + +import org.mozilla.javascript.Scriptable +import java.io.File +import org.mozilla.javascript.Context +import scala.collection.Map + +class ScalaJSCoreLib(file: File, + scalaJSProviders: Map[String, File] = Map.empty) { + + import ScalaJSCoreLib.Info + + def withProviders(scalaJSProviders: Map[String, File]) = + new ScalaJSCoreLib(this.file, scalaJSProviders) + + def insertInto(context: Context, scope: Scriptable) = { + context.evaluateFile(scope, file) + lazifyScalaJSFields(scope) + } + + private val scalaJSLazyFields = Seq( + Info("data"), + Info("c"), + Info("inheritable"), + Info("classes"), + Info("impls", isTraitImpl = true), + Info("moduleInstances", isModule = true), + Info("modules", isModule = true), + Info("is"), + Info("as"), + Info("isArrayOf"), + Info("asArrayOf")) + + private def lazifyScalaJSFields(scope: Scriptable) = { + val ScalaJS = scope.get("ScalaJS", scope).asInstanceOf[Scriptable] + + def makeLazyScalaJSScope(base: Scriptable, isModule: Boolean, isTraitImpl: Boolean) = + new LazyScalaJSScope(scalaJSProviders, scope, base, isModule, isTraitImpl) + + for (Info(name, isModule, isTraitImpl) <- scalaJSLazyFields) { + val base = ScalaJS.get(name, ScalaJS).asInstanceOf[Scriptable] + val lazified = makeLazyScalaJSScope(base, isModule, isTraitImpl) + ScalaJS.put(name, ScalaJS, lazified) + } + } +} + +object ScalaJSCoreLib { + private case class Info(name: String, + isModule: Boolean = false, isTraitImpl: Boolean = false) +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/Utilities.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/Utilities.scala new file mode 100644 index 0000000000..043af65f12 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/Utilities.scala @@ -0,0 +1,78 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.environment.rhino + +import scala.annotation.tailrec + +import org.mozilla.javascript +import org.mozilla.javascript.Context +import org.mozilla.javascript.NativeObject +import org.mozilla.javascript.Scriptable +import org.mozilla.javascript.Scriptable.NOT_FOUND +import org.mozilla.javascript.ScriptableObject + +trait Utilities { self: CodeBlock => + + def getObject(base: String, className: String) = { + val classNameKey = className.replaceAll("\\.", "_") + + val classObject = + getFrom(scope, s"$base.$classNameKey".split("\\."), scope) + + classObject match { + case Right(classObject: javascript.Function) => + classObject + case Right(noFunction) => + throw new RuntimeException( + s"Could not find constructor function, type of element was: ${noFunction.getClass.getName}") + case Left(key) => + throw new RuntimeException( + s"Could not find $className, undefined key: $key") + } + } + + def toArgs(args: Seq[Any]) = args.map(Context.javaToJS(_, scope)).toArray + + def createInstance(className: String, args: Any*) = { + val classObject = getObject("ScalaJS.classes", className) + classObject.construct(context, scope, toArgs(args)) + } + + def getModule(className: String) = { + val moduleObject = getObject("ScalaJS.modules", className) + moduleObject.call(context, scope, scope, Array.empty).asInstanceOf[NativeObject] + } + + def callMethod(obj: NativeObject, methodName: String, args: Any*) = { + val method = ScriptableObject.getProperty(obj, methodName) + + method match { + case NOT_FOUND => + throw new RuntimeException(s"Could not find method $methodName") + case method: javascript.Function => + method.call(context, scope, obj, toArgs(args)) + case other => + throw new RuntimeException( + s"$methodName is not a method, type was: ${other.getClass.getName}") + } + } + + @tailrec + private def getFrom(obj: Scriptable, keys: Seq[String], + scope: Scriptable): Either[String, Scriptable] = { + if (keys.isEmpty) Right(obj) + else { + val key = keys.head + val result = obj.get(key, scope) + if (result == NOT_FOUND) Left(key) + else getFrom(result.asInstanceOf[Scriptable], keys.tail, scope) + } + } +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/package.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/package.scala new file mode 100644 index 0000000000..0efd6a7891 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/environment/rhino/package.scala @@ -0,0 +1,49 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.environment + +import java.io.File + +import org.mozilla.javascript.Scriptable +import org.mozilla.javascript.Context +import org.mozilla.javascript.BaseFunction +import org.mozilla.javascript.Undefined +import org.mozilla.javascript.ScriptableObject + +package object rhino { + + implicit class ContextOps(val self: Context) extends AnyVal { + def evaluateFile(scope: Scriptable, file: File, + securityDomain: AnyRef = null): Any = { + val reader = new java.io.FileReader(file) + try { + self.evaluateReader(scope, reader, + file.getAbsolutePath, 1, securityDomain) + } finally { + reader.close() + } + } + } + + implicit class ScriptableObjectOps(val self: ScriptableObject) { + def addFunction(name: String, function: Array[AnyRef] => Unit) = { + val rhinoFunction = + new BaseFunction { + override def call(context: Context, scope: Scriptable, + thisObj: Scriptable, args: Array[AnyRef]): AnyRef = { + function(args) + Undefined.instance + } + } + + ScriptableObject.putProperty(self, name, rhinoFunction) + } + } +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMappedException.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMappedException.scala new file mode 100644 index 0000000000..3fd5721210 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMappedException.scala @@ -0,0 +1,36 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.sourcemap + +import org.mozilla.javascript.ScriptStackElement +import org.mozilla.javascript.RhinoException + +case class SourceMappedException( + message: String, + stack: Array[ScriptStackElement], + cause: RhinoException) extends RuntimeException(message, cause) { + + def this(e: RhinoException) = + this(e.getMessage, e.getScriptStack, e) + + def this(message: String, stack: Array[ScriptStackElement]) = + this(message, stack, null) + + val javaStyleStack = + stack.map { el => + new StackTraceElement( + "", + Option(el.functionName).getOrElse(""), + el.fileName, + el.lineNumber) + } + + setStackTrace(javaStyleStack) +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMapper.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMapper.scala new file mode 100644 index 0000000000..7203acdbca --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/sourcemap/SourceMapper.scala @@ -0,0 +1,65 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.sourcemap + +import org.mozilla.javascript.ScriptStackElement +import sbt.IO +import java.io.File +import com.google.debugging.sourcemap.SourceMapConsumerFactory +import com.google.debugging.sourcemap.SourceMapSection +import com.google.debugging.sourcemap.SourceMapConsumerV3 +import com.google.debugging.sourcemap.FilePosition + +object SourceMapper { + def map(stack: Array[ScriptStackElement]): Array[ScriptStackElement] = { + for (el <- stack) yield { + val sourceMapFile = new File(el.fileName + ".map") + if (sourceMapFile.exists) mapScriptStackElement(el, sourceMapFile) + else el + } + } + + def mapScriptStackElement(el: ScriptStackElement, + sourceMapFile: File): ScriptStackElement = { + val sourceMapConsumer = + SourceMapConsumerFactory + .parse(IO.read(sourceMapFile)) + .asInstanceOf[SourceMapConsumerV3] + + val lineNumber = el.lineNumber + val column = getFirstColumn(sourceMapConsumer, lineNumber) + + val originalMapping = + sourceMapConsumer.getMappingForLine(lineNumber, column) + + new ScriptStackElement( + originalMapping.getOriginalFile, + el.functionName, + originalMapping.getLineNumber) + } + + def getFirstColumn(sourceMapConsumer: SourceMapConsumerV3, lineNumber: Int) = { + var column: Option[Int] = None + + sourceMapConsumer.visitMappings( + new SourceMapConsumerV3.EntryVisitor { + def visit(sourceName: String, + symbolName: String, + sourceStartPosition: FilePosition, + startPosition: FilePosition, + endPosition: FilePosition): Unit = + // we need to adjust line and column numbers because they are 0 based + if (!column.isDefined && startPosition.getLine == lineNumber - 1) + column = Some(startPosition.getColumn + 1) + }) + + column.getOrElse(1) + } +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/EventProxy.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/EventProxy.scala new file mode 100644 index 0000000000..ae86828af4 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/EventProxy.scala @@ -0,0 +1,94 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.testing + +import org.mozilla.javascript.ScriptStackElement +import org.mozilla.javascript.NativeArray +import sbt.testing.Logger +import sbt.testing.EventHandler +import org.mozilla.javascript.NativeObject +import scala.scalajs.sbtplugin.sourcemap.SourceMapper +import scala.scalajs.sbtplugin.sourcemap.SourceMappedException + +case class EventProxy(handler: EventHandler, loggers: Array[Logger], events: Events) { + + import events._ + + private[testing] def error(message: String, stack: Array[ScriptStackElement]): Unit = { + val sourceMappedStack = SourceMapper.map(stack) + logWithEvent(_.error, + messageWithStack(message, sourceMappedStack), + Error(new SourceMappedException(message, sourceMappedStack))) + } + + def error(message: String, stack: NativeArray): Unit = + error(message, fromNativeArray(stack)) + + def failure(message: String, stack: NativeArray): Unit = + failure(message, fromNativeArray(stack)) + + private[testing] def failure(message: String, stack: Array[ScriptStackElement]) = { + logWithEvent(_.error, + messageWithStack(message, stack), + Failure(new SourceMappedException(message, stack))) + } + + def succeeded(message: String): Unit = + logWithEvent(_.info, message, Succeeded) + + def skipped(message: String): Unit = + logWithEvent(_.info, message, Skipped) + + def pending(message: String): Unit = + logWithEvent(_.info, message, Pending) + + def ignored(message: String): Unit = + logWithEvent(_.info, message, Ignored) + + def canceled(message: String): Unit = + logWithEvent(_.info, message, Canceled) + + def info(message: String): Unit = + log(_.info, message) + + def error(message: String): Unit = + log(_.error, message) + + private def messageWithStack(message: String, stack: Array[ScriptStackElement]): String = + message + stack.mkString("\n", "\n", "") + + private def log(method: Logger => (String => Unit), message: String): Unit = { + for (logger <- loggers) { + var loggedMessage = message + if (!logger.ansiCodesSupported) removeColors(loggedMessage) + method(logger)(loggedMessage) + } + } + + private def logWithEvent(method: Logger => (String => Unit), message: String, event: Event): Unit = { + handler handle event + log(method, message) + } + + private def fromNativeArray(stack: NativeArray) = { + stack.toArray.map { + case o: NativeObject => + new ScriptStackElement( + o.get("fileName").asInstanceOf[String], + o.get("functioName").asInstanceOf[String], + o.get("lineNumber").asInstanceOf[Double].toInt) + } + } + + private val colorPattern = raw"\033\[\d{1, 2}m" + + private def removeColors(message: String): String = + message.replaceAll(colorPattern, "") +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/Events.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/Events.scala new file mode 100644 index 0000000000..7d7fc82e29 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/Events.scala @@ -0,0 +1,39 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.testing + +import sbt.testing.TaskDef +import sbt.testing.OptionalThrowable +import sbt.testing.{ Event => SbtEvent } +import sbt.testing.Status +import sbt.testing.SuiteSelector + +class Events(taskDef: TaskDef) { + + abstract class Event(val status: Status, + val throwable: OptionalThrowable = new OptionalThrowable) extends SbtEvent { + val fullyQualifiedName = taskDef.fullyQualifiedName + val fingerprint = taskDef.fingerprint + val selector = taskDef.selectors.headOption.getOrElse(new SuiteSelector) + val duration = -1L + } + + case class Error(exception: Throwable) extends Event( + Status.Error, new OptionalThrowable(exception)) + + case class Failure(exception: Throwable) extends Event( + Status.Failure, new OptionalThrowable(exception)) + + case object Succeeded extends Event(Status.Success) + case object Skipped extends Event(Status.Skipped) + case object Pending extends Event(Status.Pending) + case object Ignored extends Event(Status.Ignored) + case object Canceled extends Event(Status.Canceled) +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestFramework.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestFramework.scala new file mode 100644 index 0000000000..4867db4ad0 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestFramework.scala @@ -0,0 +1,34 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.testing + +import scala.scalajs.sbtplugin.ScalaJSEnvironment +import sbt.testing.Fingerprint +import sbt.testing.Framework +import sbt.testing.SubclassFingerprint +import sbt.testing.Runner + +class TestFramework(environment: ScalaJSEnvironment, testRunnerClass: String, + testFramework: String) extends Framework { + + val name = "Scala.js Test Framework" + + lazy val fingerprints = Array[Fingerprint](f1) + + val f1 = new SubclassFingerprint { + val isModule = true + val superclassName = "scala.scalajs.test.Test" + val requireNoArgConstructor = true + } + + def runner(args: Array[String], remoteArgs: Array[String], + testClassLoader: ClassLoader): Runner = + TestRunner(args, remoteArgs, environment, testRunnerClass, testFramework) +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestRunner.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestRunner.scala new file mode 100644 index 0000000000..66d8a6c3c4 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestRunner.scala @@ -0,0 +1,32 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.testing + +import sbt.testing.TaskDef +import sbt.testing.Task +import sbt.testing.Runner +import scala.scalajs.sbtplugin.ScalaJSEnvironment + +case class TestRunner( + args: Array[String], remoteArgs: Array[String], + environment: ScalaJSEnvironment, + testRunnerClass: String, testFramework: String) extends Runner { + + def tasks(taskDefs: Array[TaskDef]): Array[Task] = + if (_done) throw new IllegalStateException("Done has already been called") + else taskDefs.map(TestTask(environment, testRunnerClass, testFramework)) + + def done(): String = { + _done = true + "" + } + + private var _done = false +} diff --git a/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestTask.scala b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestTask.scala new file mode 100644 index 0000000000..192461c5b0 --- /dev/null +++ b/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestTask.scala @@ -0,0 +1,52 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.sbtplugin.testing + +import sbt.testing.TaskDef +import sbt.testing.EventHandler +import sbt.testing.Task +import sbt.testing.Logger +import scala.scalajs.sbtplugin.ScalaJSEnvironment +import scala.annotation.tailrec +import org.mozilla.javascript.Context +import org.mozilla.javascript.Scriptable +import org.mozilla.javascript +import org.mozilla.javascript.Scriptable.NOT_FOUND +import org.mozilla.javascript.RhinoException +import scala.scalajs.sbtplugin.environment.rhino.CodeBlock +import scala.scalajs.sbtplugin.environment.rhino.Utilities + +case class TestTask( + environment: ScalaJSEnvironment, + testRunnerClass: String, + testFramework: String)(val taskDef: TaskDef) extends Task { + + val tags = Array.empty[String] + + def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = { + val testKey = taskDef.fullyQualifiedName.replaceAll("\\.", "_") + val testFrameworkKey = testFramework.replaceAll("\\.", "_") + + val eventProxy = EventProxy(eventHandler, loggers, new Events(taskDef)) + + environment.runInContextAndScope { (context, scope) => + new CodeBlock(context, scope) with Utilities { + try { + createInstance(testRunnerClass, eventProxy, testFrameworkKey, testKey) + } catch { + case t: RhinoException => + eventProxy.error(t.details, t.getScriptStack()) + } + } + } + + Array.empty + } +} diff --git a/test/src/test/resources/bootstrap.js b/test/src/test/resources/bootstrap.js deleted file mode 100644 index 981b5f95d7..0000000000 --- a/test/src/test/resources/bootstrap.js +++ /dev/null @@ -1,25 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js Test Suite ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - -/** - * This file is loaded first and used for patching the JS environment. - */ - -function scalaJSStub(name) { - return function() { - console.log("Stub for " + name + " called"); - } -}; - -/* Stub-out timer methods used by Jasmine and not provided by Rhino. */ -if (typeof setTimeout == 'undefined') { - var setTimeout = scalaJSStub('setTimeout'); - var clearTimeout = scalaJSStub('clearTimeout'); - var setInterval = scalaJSStub('setInterval'); - var clearInterval = scalaJSStub('clearInterval'); -} diff --git a/test/src/test/resources/jasmine_rhino_reporter.js b/test/src/test/resources/jasmine_rhino_reporter.js deleted file mode 100644 index 4b0943a6dd..0000000000 --- a/test/src/test/resources/jasmine_rhino_reporter.js +++ /dev/null @@ -1,142 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js Test Suite ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - -/** - * Console-oriented reporter for Rhino. Colorizes output based on the value of - * the TERM env variable. - * - * Usage: - * - * jasmine.getEnv().addReporter(new jasmine.RhinoReporter()); - * jasmine.getEnv().execute(); - */ - -(function() { - if (!jasmine) { - throw new Exception("jasmine library does not exist in global namespace!"); - } - - var RhinoReporter = function() { - }; - - RhinoReporter.prototype = { - reportRunnerResults : function(runner) { - var out = this.failed ? failed : passed - out("%.0f spec(s), %.0f failure(s) in %.3fs.", this.executed, - this.failed, (now() - this.started) / 1000); - }, - - reportRunnerStarting : function(runner) { - this.started = now(); - this.executed = 0; - this.passed = 0; - this.failed = 0; - }, - - reportSpecResults : function(spec) { - var results = spec.results(); - if (results.passed()) { - passed(" %s", spec.description); - } else { - failed(" %s", spec.description); - - for (var i = 0; i < results.getItems().length; i++) { - var result = results.getItems()[i]; - - if (result.type == 'log') { - print(" %s", result.toString()); - } else if (result.type == 'expect') { - if (result.passed && result.passed()) { - print(" ✓ %s", result.message); - } else { - print(" ☹ %s", result.message); - if (result.trace.stack) { - print("%s", result.trace.stack); - } - } - } - } - } - }, - - reportSpecStarting : function(spec) { - if (this.suite != spec.suite) { - this.suite = spec.suite; - suite(this.suite.description); - } - }, - - reportSuiteResults : function(suite) { - var results = suite.results(); - if (results.passedCount != results.totalCount) - failed("%.0f of %.0f failed", results.failedCount, results.totalCount); - this.passed += results.passedCount; - this.failed += results.failedCount; - this.executed += results.totalCount; - print(); - }, - - log : function(str) { - print("%s", str); - } - }; - - function now() { - return (new Date()).getTime(); - } - - function suite(str) { - print("%s", format(arguments)); - } - - function passed(str) { - print("%s%s%s", Color.GREEN, format(arguments), Color.RESET); - } - - function failed(str) { - print("%s%s%s", Color.RED, format(arguments), Color.RESET); - } - - function print() { - var msg = arguments.length > 0 ? format(arguments) : ""; - console.log(msg); - } - - function format(args) { - return java.lang.String.format(args[0], args[1], args[2], args[3], args[4], - args[5], args[6], args[7], args[8], args[9], args[10]); - } - - var Color = { - RESET : "\033[m", - GREEN : "\033[32m", - RED : "\033[31m", - BLUE : "\033[34m" - }; - - var ColorTerminals = [ 'xterm' ]; - - function isColorTerm() { - var term = java.lang.System.getenv("TERM"); - - for (var i in ColorTerminals) { - if (term == ColorTerminals[i]) { - return true; - } - } - return false; - } - - if (!isColorTerm()) { - for (var i in Color) { - Color[i] = ''; - } - } - - jasmine.RhinoReporter = RhinoReporter; -})(); diff --git a/test/src/test/resources/main.js b/test/src/test/resources/main.js deleted file mode 100644 index e12a0b4b05..0000000000 --- a/test/src/test/resources/main.js +++ /dev/null @@ -1,39 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js Test Suite ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - -console.log("Starting Scala.js test suite"); -console.log(""); - -/* Load all test suites ... */ -ScalaJS.modules.scala_scalajs_test_compiler_InteroperabilityTest(); -ScalaJS.modules.scala_scalajs_test_compiler_RegressionTest(); -ScalaJS.modules.scala_scalajs_test_compiler_LongTest(); - -ScalaJS.modules.scala_scalajs_test_javalib_ObjectTest(); -ScalaJS.modules.scala_scalajs_test_javalib_IntegerTest(); -ScalaJS.modules.scala_scalajs_test_javalib_StringTest(); -ScalaJS.modules.scala_scalajs_test_javalib_ArraysTest(); -ScalaJS.modules.scala_scalajs_test_javalib_LongTest(); -ScalaJS.modules.scala_scalajs_test_javalib_ThrowablesTest(); -ScalaJS.modules.scala_scalajs_test_javalib_FormatterTest(); - -ScalaJS.modules.scala_scalajs_test_scalalib_EnumerationTest(); - -ScalaJS.modules.scala_scalajs_test_jsinterop_ArrayTest(); -ScalaJS.modules.scala_scalajs_test_jsinterop_DictionaryTest(); -ScalaJS.modules.scala_scalajs_test_jsinterop_DynamicTest(); -ScalaJS.modules.scala_scalajs_test_jsinterop_RuntimeLongTest(); - -/* ... and run them. */ -var jasmineEnv = jasmine.getEnv(); -jasmineEnv.addReporter(new jasmine.RhinoReporter()); -jasmineEnv.updateInterval = 0; -jasmineEnv.execute(); - -if (jasmineEnv.currentRunner().results().failedCount > 0) - throw new Error("Some tests failed") diff --git a/test/src/test/scala/scala/scalajs/test/compiler/InteroperabilityTest.scala b/test/src/test/scala/scala/scalajs/test/compiler/InteroperabilityTest.scala index 8f6a05edfc..e1b88b40f3 100644 --- a/test/src/test/scala/scala/scalajs/test/compiler/InteroperabilityTest.scala +++ b/test/src/test/scala/scala/scalajs/test/compiler/InteroperabilityTest.scala @@ -9,7 +9,7 @@ package scala.scalajs.test package compiler import scala.scalajs.js -import scala.scalajs.test.ScalaJSTest +import scala.scalajs.test.JasmineTest import scala.scalajs.js.annotation.JSName import scala.scalajs.js.annotation.JSBracketAccess @@ -17,7 +17,7 @@ import scala.scalajs.js.annotation.JSBracketAccess * Based on examples in: * http://lampwww.epfl.ch/~doeraene/scala-js/doc/js-interoperability.html */ -object InteroperabilityTest extends ScalaJSTest { +object InteroperabilityTest extends JasmineTest { describe("JavaScript interoperability") { diff --git a/test/src/test/scala/scala/scalajs/test/compiler/LongTest.scala b/test/src/test/scala/scala/scalajs/test/compiler/LongTest.scala index fb6b7af895..c61094cb9d 100644 --- a/test/src/test/scala/scala/scalajs/test/compiler/LongTest.scala +++ b/test/src/test/scala/scala/scalajs/test/compiler/LongTest.scala @@ -8,7 +8,6 @@ package scala.scalajs.test package compiler -import scala.scalajs.test.ScalaJSTest import scala.scalajs.js /** @@ -17,7 +16,7 @@ import scala.scalajs.js * see scala.scalajs.test.jsinterop.RuntimeLongTest * for a test of the implementation itself */ -object LongTest extends ScalaJSTest { +object LongTest extends JasmineTest { describe("JavaScript 64-bit long compatibility") { it("should correctly handle literals") { diff --git a/test/src/test/scala/scala/scalajs/test/compiler/RegressionTest.scala b/test/src/test/scala/scala/scalajs/test/compiler/RegressionTest.scala index a6010a96df..a3dbe38e97 100644 --- a/test/src/test/scala/scala/scalajs/test/compiler/RegressionTest.scala +++ b/test/src/test/scala/scala/scalajs/test/compiler/RegressionTest.scala @@ -9,9 +9,9 @@ package scala.scalajs.test package compiler import scala.scalajs.js -import scala.scalajs.test.ScalaJSTest +import scala.scalajs.test.JasmineTest -object RegressionTest extends ScalaJSTest { +object RegressionTest extends JasmineTest { describe("Scala.js compiler regression tests") { diff --git a/test/src/test/scala/scala/scalajs/test/javalib/ArraysTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/ArraysTest.scala index 7cf62efcfc..dd17dcd18a 100644 --- a/test/src/test/scala/scala/scalajs/test/javalib/ArraysTest.scala +++ b/test/src/test/scala/scala/scalajs/test/javalib/ArraysTest.scala @@ -9,11 +9,10 @@ package scala.scalajs.test package javalib import scala.scalajs.js -import scala.scalajs.test.ScalaJSTest - +import scala.scalajs.test.JasmineTest import java.util.{ Arrays, Comparator } -object ArraysTest extends ScalaJSTest { +object ArraysTest extends JasmineTest { val stringComparator = new Comparator[String]() { def compare(s1: String, s2: String) = s1.compareTo(s2) diff --git a/test/src/test/scala/scala/scalajs/test/javalib/FormatterTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/FormatterTest.scala index 3ead31fc03..ca3c14a065 100644 --- a/test/src/test/scala/scala/scalajs/test/javalib/FormatterTest.scala +++ b/test/src/test/scala/scala/scalajs/test/javalib/FormatterTest.scala @@ -9,12 +9,12 @@ package scala.scalajs.test package javalib import scala.scalajs.js -import scala.scalajs.test.ScalaJSTest +import scala.scalajs.test.JasmineTest import java.util.{ Formatter, Formattable, FormattableFlags } import java.lang.{ - Double => JDouble, + Double => JDouble, Float => JFloat, Integer => JInteger, Long => JLong, @@ -22,9 +22,9 @@ import java.lang.{ Boolean => JBoolean, String => JString } - -object FormatterTest extends ScalaJSTest { + +object FormatterTest extends JasmineTest { class HelperClass class FormattableClass extends Formattable { diff --git a/test/src/test/scala/scala/scalajs/test/javalib/IntegerTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/IntegerTest.scala index b2d45a5fc2..b50022097c 100644 --- a/test/src/test/scala/scala/scalajs/test/javalib/IntegerTest.scala +++ b/test/src/test/scala/scala/scalajs/test/javalib/IntegerTest.scala @@ -8,10 +8,10 @@ package scala.scalajs.test package javalib -import scala.scalajs.test.ScalaJSTest +import scala.scalajs.test.JasmineTest import scala.scalajs.js.Any.fromInt -object IntegerTest extends ScalaJSTest { +object IntegerTest extends JasmineTest { describe("java.lang.Integer") { diff --git a/test/src/test/scala/scala/scalajs/test/javalib/LongTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/LongTest.scala index 7a0049a357..04f3c277b4 100644 --- a/test/src/test/scala/scala/scalajs/test/javalib/LongTest.scala +++ b/test/src/test/scala/scala/scalajs/test/javalib/LongTest.scala @@ -8,14 +8,13 @@ package scala.scalajs.test package javalib -import scala.scalajs.test.ScalaJSTest import java.lang.Long /** * tests the implementation of the java standard library Long * requires jsinterop/LongTest to work to make sense */ -object LongTest extends ScalaJSTest { +object LongTest extends JasmineTest { describe("java.lang.Long") { it("should implement bitCount") { diff --git a/test/src/test/scala/scala/scalajs/test/javalib/ObjectTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/ObjectTest.scala index 9c24528e71..d9cc187db6 100644 --- a/test/src/test/scala/scala/scalajs/test/javalib/ObjectTest.scala +++ b/test/src/test/scala/scala/scalajs/test/javalib/ObjectTest.scala @@ -8,9 +8,9 @@ package scala.scalajs.test package javalib -import scala.scalajs.test.ScalaJSTest +import scala.scalajs.test.JasmineTest -object ObjectTest extends ScalaJSTest { +object ObjectTest extends JasmineTest { describe("java.lang.Object") { diff --git a/test/src/test/scala/scala/scalajs/test/javalib/StringTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/StringTest.scala index c42f4c73f0..9ba4801e12 100644 --- a/test/src/test/scala/scala/scalajs/test/javalib/StringTest.scala +++ b/test/src/test/scala/scala/scalajs/test/javalib/StringTest.scala @@ -9,9 +9,9 @@ package scala.scalajs.test package javalib import scala.scalajs.js -import scala.scalajs.test.ScalaJSTest +import scala.scalajs.test.JasmineTest -object StringTest extends ScalaJSTest { +object StringTest extends JasmineTest { describe("java.lang.String") { diff --git a/test/src/test/scala/scala/scalajs/test/javalib/ThrowablesTest.scala b/test/src/test/scala/scala/scalajs/test/javalib/ThrowablesTest.scala index 1f10098c4f..93aac0b214 100644 --- a/test/src/test/scala/scala/scalajs/test/javalib/ThrowablesTest.scala +++ b/test/src/test/scala/scala/scalajs/test/javalib/ThrowablesTest.scala @@ -8,9 +8,7 @@ package scala.scalajs.test package javalib -import scala.scalajs.test.ScalaJSTest - -object ThrowablesTest extends ScalaJSTest { +object ThrowablesTest extends JasmineTest { describe("java.lang.Throwables, java.util.Throwables") { it("should define all java.lang and java.util Errors/Exceptions") { @@ -69,10 +67,10 @@ object ThrowablesTest extends ScalaJSTest { new ConcurrentModificationException() new DuplicateFormatFlagsException("") new EmptyStackException() - new FormatFlagsConversionMismatchException("", '\0') + new FormatFlagsConversionMismatchException("", '\u0000') new FormatterClosedException() new IllegalFormatCodePointException(0) - new IllegalFormatConversionException('\0', new Object().getClass) + new IllegalFormatConversionException('\u0000', new Object().getClass) new IllegalFormatFlagsException("") new IllegalFormatPrecisionException(0) new IllegalFormatWidthException(0) diff --git a/test/src/test/scala/scala/scalajs/test/jsinterop/ArrayTest.scala b/test/src/test/scala/scala/scalajs/test/jsinterop/ArrayTest.scala index 7258257a93..b5a40ef927 100644 --- a/test/src/test/scala/scala/scalajs/test/jsinterop/ArrayTest.scala +++ b/test/src/test/scala/scala/scalajs/test/jsinterop/ArrayTest.scala @@ -9,9 +9,9 @@ package scala.scalajs.test package jsinterop import scala.scalajs.js -import scala.scalajs.test.ScalaJSTest +import scala.scalajs.test.JasmineTest -object ArrayTest extends ScalaJSTest { +object ArrayTest extends JasmineTest { describe("scala.scalajs.js.Array") { diff --git a/test/src/test/scala/scala/scalajs/test/jsinterop/DictionaryTest.scala b/test/src/test/scala/scala/scalajs/test/jsinterop/DictionaryTest.scala index d1df5f865a..0887ac2276 100644 --- a/test/src/test/scala/scala/scalajs/test/jsinterop/DictionaryTest.scala +++ b/test/src/test/scala/scala/scalajs/test/jsinterop/DictionaryTest.scala @@ -9,9 +9,9 @@ package scala.scalajs.test package jsinterop import scala.scalajs.js -import scala.scalajs.test.ScalaJSTest +import scala.scalajs.test.JasmineTest -object DictionaryTest extends ScalaJSTest { +object DictionaryTest extends JasmineTest { describe("scala.scalajs.js.Dictionary") { diff --git a/test/src/test/scala/scala/scalajs/test/jsinterop/DynamicTest.scala b/test/src/test/scala/scala/scalajs/test/jsinterop/DynamicTest.scala index a5e10d9e58..9e7ddd6c5e 100644 --- a/test/src/test/scala/scala/scalajs/test/jsinterop/DynamicTest.scala +++ b/test/src/test/scala/scala/scalajs/test/jsinterop/DynamicTest.scala @@ -9,9 +9,9 @@ package scala.scalajs.test package jsinterop import scala.scalajs.js -import scala.scalajs.test.ScalaJSTest +import scala.scalajs.test.JasmineTest -object DynamicTest extends ScalaJSTest { +object DynamicTest extends JasmineTest { describe("scala.scalajs.js.Dynamic") { diff --git a/test/src/test/scala/scala/scalajs/test/jsinterop/RuntimeLongTest.scala b/test/src/test/scala/scala/scalajs/test/jsinterop/RuntimeLongTest.scala index 1164b61be2..0c288b9cb7 100644 --- a/test/src/test/scala/scala/scalajs/test/jsinterop/RuntimeLongTest.scala +++ b/test/src/test/scala/scala/scalajs/test/jsinterop/RuntimeLongTest.scala @@ -8,14 +8,14 @@ package scala.scalajs.test package jsinterop -import scala.scalajs.test.ScalaJSTest import scala.scalajs.runtime.Long +import org.scalajs.jasmine.JasmineExpectation /** * test the runtime Long implementation directly * does not depend on magic compiler Long rewriting */ -object RuntimeLongTest extends ScalaJSTest { +object RuntimeLongTest extends JasmineTest { /** overload expect for long to add toString */ def expect(l: Long): JasmineExpectation = expect(l.toHexString) @@ -52,7 +52,7 @@ object RuntimeLongTest extends ScalaJSTest { expect(Long.fromInt(7) * Long.fromInt(15)).toEqual("0000000000000069") expect(Long.fromInt(-7) * Long.fromInt(15)).toEqual("ffffffffffffff97") expect(maxInt * maxInt).toEqual( "3fffffff00000001") - expect(Long.fromHexString("001000000000000e") * + expect(Long.fromHexString("001000000000000e") * Long.fromInt(-4)).toEqual("ffbfffffffffffc8") } @@ -91,7 +91,7 @@ object RuntimeLongTest extends ScalaJSTest { expect(Long.fromInt(5).toDouble).toEqual(5.0) expect((maxInt+one).toDouble).toEqual(2147483648.0) } - + it("should correctly implement fromString") { expect(Long.fromString("4")).toEqual( "0000000000000004") expect(Long.fromString("-4")).toEqual( "fffffffffffffffc") diff --git a/test/src/test/scala/scala/scalajs/test/scalalib/EnumerationTest.scala b/test/src/test/scala/scala/scalajs/test/scalalib/EnumerationTest.scala index 884eaa8fd5..80e214588f 100644 --- a/test/src/test/scala/scala/scalajs/test/scalalib/EnumerationTest.scala +++ b/test/src/test/scala/scala/scalajs/test/scalalib/EnumerationTest.scala @@ -8,9 +8,9 @@ package scala.scalajs.test package scalalib -import scala.scalajs.test.ScalaJSTest +import scala.scalajs.test.JasmineTest -object EnumerationTest extends ScalaJSTest { +object EnumerationTest extends JasmineTest { describe("scala.Enumeration") {