diff --git a/.travis.yml b/.travis.yml index e06477754..6bcc07360 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,23 +25,28 @@ matrix: # jdk: oraclejdk9 # - env: JDK='OpenJDK 9' # install: . ./install-jdk.sh -F 9 - # 10 - - env: JDK='Oracle JDK 10' - jdk: oraclejdk10 - - env: JDK='OpenJDK 10' - install: . ./install-jdk.sh -F 10 -L GPL - # 11 - - env: JDK='Oracle JDK 11' - install: . ./install-jdk.sh -F 11 -L BCL +# JDK 10 is EOL +# # 10 +# - env: JDK='Oracle JDK 10' +# jdk: oraclejdk10 +# - env: JDK='OpenJDK 10' +# install: . ./install-jdk.sh -F 10 -L GPL +# # 11 (Oracle and OpenJDK are pretty similar) +# - env: JDK='Oracle JDK 11' +# install: . ./install-jdk.sh -F 11 -L BCL - env: JDK='OpenJDK 11' install: . ./install-jdk.sh -F 11 -L GPL # GraalVM - env: JDK='GraalVM 1.0.0' - install: . ./install-jdk.sh --url https://github.com/oracle/graal/releases/download/vm-1.0.0-rc7/graalvm-ce-1.0.0-rc7-linux-amd64.tar.gz + install: . ./install-jdk.sh --url https://github.com/oracle/graal/releases/download/vm-1.0.0-rc9/graalvm-ce-1.0.0-rc9-linux-amd64.tar.gz + # OpenJ9 + - env: JDK='OpenJ9 13' + install: . ./install-jdk.sh --url https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.1%2B13/OpenJDK11-jdk_x64_linux_openj9_11.0.1_13.tar.gz allow_failures: - # Non LTS releases are OK to fail - - env: JDK='Oracle JDK 10' - - env: JDK='OpenJDK 10' +# # Non LTS releases are OK to fail +# - env: JDK='OpenJDK 9' +# - env: JDK='OpenJDK 10' + - env: JDK='OpenJ9 13' script: - echo PATH = ${PATH} @@ -50,7 +55,8 @@ script: - mvn --settings settings.xml test -B after_success: -- bash <(curl -s https://codecov.io/bash) +- '[[ "${JDK}" = "GraalVM 1.0.0" ]] && + bash <(curl -s https://codecov.io/bash) || true' - '[[ "${TRAVIS_BRANCH}" = "develop" ]] && [[ "${TRAVIS_PULL_REQUEST}" = "false" ]] && [[ "${JDK}" = "OpenJDK 11" ]] && diff --git a/CHANGELOG.md b/CHANGELOG.md index 2903fa5ab..0d920444c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +- Added a package command to package either fat jar or a JVMCI fat jar. +- Fix issue preventing GraalJS running on OpenJ9 +- Allow specifying absolute path as start module - implemented module aliases - fixed node inspector paths for debugging - fixed runtime definitions diff --git a/README.md b/README.md index adab897dc..43c7282a0 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,26 @@ suite.run(); Profit! -# Documentation +## JDK11 + +If you don't want to run on GraalVM JDK but prefer [OpenJDK11](https://adoptopenjdk.net/index.html?variant=openjdk11&jvmVariant=hotspot), +or even [OpenJ9](https://adoptopenjdk.net/?variant=openjdk11&jvmVariant=openj9) all you need to do is **prefix** +any `NPM` / `Maven` command with the variable `JVMCI=1`, e.g.: + +```bash +JVMCI=1 npm install +JVMCI=1 npm start +``` + +This will assume you're running on plain JDK11, if you don't want to type the variable all the time or set it to your +environment, you can add it to your `package.json` as: + +```json +{ + "jvmci": true +} +``` + +## Documentation For more documentation please see [docs](./docs). diff --git a/docs/README.md b/docs/README.md index 910d24c87..6ed7e12dc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -64,7 +64,8 @@ add the package [es4x-cli](https://www.npmjs.com/package/es4x-cli) to your "postinstall": "es4x postinstall", "test": "es4x launcher test", "start": "es4x launcher run", - "shell": "es4x shell" + "shell": "es4x shell", + "package": "es4x package" }, "license": "ISC", "private": true, @@ -80,8 +81,8 @@ As of this moment you can follow the normal workflow. For example in order to cr ```json { "dependencies": { - "@vertx/core": "3.5.3", - "@vertx/web": "3.5.3" + "@vertx/core": "3.5.4", + "@vertx/web": "3.5.4" } } ``` @@ -163,7 +164,7 @@ It is common to package JVM applications as runnable `JAR` files, the `es4x-cli` `maven-shade-plugin` configured for this: ```sh -mvn clean package +npm run package ``` And a new `JAR` file should be built in your `target` directory. @@ -172,6 +173,34 @@ Packaging will re-arrange your application code to be moved to the directory `no it can be used from other JARs. In order for this to work correctly, the current `node_modules` are also bundled in the jar as well as all files listed under the `files` property of your `package.json`. +When running this script you will see also the output command you need to use to run your application e.g.: + +```bash +# Running on a GraalVM JVM +Running: /home/plopes/Projects/reactiverse/es4x/examples/empty-project/mvnw ... package +Run your application with: + + java \ + -jar target/empty-project-1.0.0-bin.jar +``` + +Note that if you run with the environment flag `JVMCI` then you can run your app on JDK11 too but the start command +will be a little more complex, this is what the script tells you e.g.: + +```bash +Running: /home/plopes/Projects/reactiverse/es4x/examples/empty-project/mvnw ... package +Run your application with: + + java \ + --module-path=target/compiler \ + -XX:+UnlockExperimentalVMOptions \ + -XX:+EnableJVMCI \ + --upgrade-module-path=target/compiler/compiler.jar \ + -jar target/empty-project-1.0.0-bin-jvmci.jar +``` + +In this case you need not just the binary jar, but also the directory `target/compiler`. With this you can run using +the GraalJS engine on any JDK11. ### Shell/ REPL diff --git a/docs/graal/index.md b/docs/graal/index.md index 579c5cc31..a13fc7635 100644 --- a/docs/graal/index.md +++ b/docs/graal/index.md @@ -1,7 +1,7 @@ # GraalVM Support -ES4X has **experimental** GraalVM support. The same code will run either on Nashorn (JS Engine in JDK>=8) or GraalJS -(if run on GraalVM). +ES4X has GraalVM support. The same code will run either on Nashorn (JS Engine in JDK>=8) or GraalJS +(if run on GraalVM or a JVMCI enabled JVM). There are benefits on using GraalJS namely the updated language support >=ES6 and support out of the box for generators, promises, etc.... diff --git a/es4x-cli/.pom.xml b/es4x-cli/.pom.xml index 1c4d082d9..7d9acb074 100644 --- a/es4x-cli/.pom.xml +++ b/es4x-cli/.pom.xml @@ -24,8 +24,93 @@ 1.8 1.8 1.8 + bin + + + jvmci + + + env.JVMCI + + + + bin-jvmci + + + + + org.graalvm.truffle + truffle-api + [{{graalVersion}},) + runtime + + + org.graalvm.js + js + [{{graalVersion}},) + runtime + + + org.graalvm.tools + profiler + [{{graalVersion}},) + runtime + + + org.graalvm.tools + chromeinspector + [{{graalVersion}},) + runtime + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-graalvm-compiler + initialize + + copy + + + + + org.graalvm.compiler + compiler + {{graalVersion}} + jar + compiler.jar + + + org.graalvm.truffle + truffle-api + {{graalVersion}} + jar + truffle-api.jar + + + org.graalvm.sdk + graal-sdk + {{graalVersion}} + jar + graal-sdk.jar + + + target/compiler + + + + + + + + + {{#dependencies}} @@ -117,6 +202,7 @@ META-INF/*.SF META-INF/*.DSA META-INF/*.RSA + **/module-info.class @@ -133,7 +219,7 @@ - ${project.build.directory}/${project.artifactId}-${project.version}-fat.jar + ${project.build.directory}/${project.artifactId}-${project.version}-${shade.classifier}.jar diff --git a/es4x-cli/bin/es4x.js b/es4x-cli/bin/es4x.js index 7a756dfd9..68d148b0a 100755 --- a/es4x-cli/bin/es4x.js +++ b/es4x-cli/bin/es4x.js @@ -12,6 +12,7 @@ const mkdirp = require('mkdirp'); const version = require('../package.json').version; const dir = process.cwd(); const isWindows = /^win/.test(process.platform); +const graalVersion = '1.0.0-rc9'; // quickly abort if there's no package.json if (!fs.existsSync(path.resolve(dir, 'package.json'))) { @@ -66,7 +67,6 @@ function exec(command, args, env, options, callback) { process.exit(err); } } - callback && callback(err); }); } @@ -108,6 +108,25 @@ function getMaven() { return path.resolve(dir, mvn); } +/** + * Helper to select java from JAVA_HOME or system maven + * + * @returns {string} the java command + */ +function getJava() { + let java = isWindows ? 'java.exe' : 'java'; + let javaHome = process.env['JAVA_HOME']; + + if (javaHome) { + let resolved = path.resolve(javaHome, 'bin/' + java); + if (fs.existsSync(resolved)) { + return resolved; + } + } + + return java; +} + function generateClassPath(callback) { const readClassPath = function () { let classPath = fs.readFileSync(path.resolve(dir, 'target/classpath.txt')).toString('UTF-8'); @@ -123,10 +142,13 @@ function generateClassPath(callback) { }; if (!fs.existsSync(path.resolve(dir, 'target/classpath.txt'))) { - let params = [ - '-f', path.resolve(dir, 'pom.xml'), - 'generate-sources' - ]; + let params = []; + + if (npm.jvmci) { + params.push('-Pjvmci'); + } + + params.push('-f', path.resolve(dir, 'pom.xml'), 'generate-sources'); return exec(getMaven(), params, process.env, { verbose: false, stopOnError: true }, readClassPath); } @@ -250,13 +272,22 @@ program files: files, dependencies: Object.values(dependencies), - packageJson: npm + packageJson: npm, + graalVersion: npm.graal || graalVersion }; try { fs.writeFileSync(path.resolve(dir, 'pom.xml'), Mustache.render(template, data)); // init the maven bits - exec(getMaven(), [], process.env, {stopOnError: true, verbose: options.verbose}); + let params = []; + + if (npm.jvmci) { + params.push('-Pjvmci'); + } + + params.push('-f', path.resolve(dir, 'pom.xml'), 'clean'); + + exec(getMaven(), params, process.env, {stopOnError: true, verbose: options.verbose}); } catch (e) { console.error(c.red.bold(e)); process.exit(1); @@ -278,6 +309,12 @@ program process.exit(1); } + // debug validation and they overlap + if (options.debug && options.inspect) { + console.error(c.red.bold('--debug and --inspect options are exclusive (choose one)')); + process.exit(1); + } + const test = ('test' === cmd); if (!args || args.length === 0) { @@ -303,6 +340,16 @@ program generateClassPath(function (classPath) { let params = []; + if (npm.jvmci || process.env['JVMCI']) { + // enable modules + params.push('--module-path=target/compiler'); + // enable JVMCI + params.push('-XX:+UnlockExperimentalVMOptions'); + params.push('-XX:+EnableJVMCI'); + // upgrade graal compiler + params.push('--upgrade-module-path=target/compiler/compiler.jar'); + } + if (options.debug) { if (options.debug === true) { console.log(c.yellow.bold('Debug socket listening at port: 9229')); @@ -348,7 +395,7 @@ program params = params.concat(args); // run the command - exec('java', params, process.env, {verbose: true}); + exec(getJava(), params, process.env, {verbose: true}); }); }); @@ -358,10 +405,20 @@ program .action(function (args) { generateClassPath(function (classPath) { - let params = [ - '-cp', - classPath - ]; + let params = []; + + if (npm.jvmci || process.env['JVMCI']) { + // enable modules + params.push('--module-path=target/compiler'); + // enable JVMCI + params.push('-XX:+UnlockExperimentalVMOptions'); + params.push('-XX:+EnableJVMCI'); + // upgrade graal compiler + params.push('--upgrade-module-path=target/compiler/compiler.jar'); + } + + params.push('-cp'); + params.push(classPath); params.push('io.reactiverse.es4x.Shell'); @@ -372,7 +429,7 @@ program // Releasing stdin process.stdin.setRawMode(false); - spawn('java', params, {stdio: [0, 1, 2]}) + spawn(getJava(), params, {stdio: [0, 1, 2]}) .on("exit", function (code) { // Don't forget to switch pseudo terminal on again process.stdin.setRawMode(true); @@ -409,6 +466,7 @@ program npm.scripts.start = 'es4x launcher run'; npm.scripts.test = 'es4x launcher test'; npm.scripts.shell = 'es4x shell'; + npm.scripts.package = 'es4x package'; try { fs.writeFileSync(path.resolve(dir, 'package.json'), JSON.stringify(npm, null, 2)); @@ -418,6 +476,55 @@ program } }); +program + .command('package') + .description('Packages the application as a runnable jar') + .action(function(options) { + + // if it doesn't exist stop + if (!fs.existsSync(path.resolve(dir, 'pom.xml'))) { + console.error(c.red.bold('No \'pom.xml\' found, please init it first.')); + process.exit(1); + } + + try { + // init the maven bits + let params = []; + + if (npm.jvmci || process.env['JVMCI']) { + params.push('-Pjvmci'); + } + + params.push('-f', path.resolve(dir, 'pom.xml'), 'clean', 'package'); + + exec(getMaven(), params, process.env, {stopOnError: true, verbose: options.verbose}, function (code) { + if (code !== 0) { + console.error(c.red.bold('Maven exited with code: ' + code)); + process.exit(1); + } + + console.log(c.green.bold('Run your application with:')); + console.log(); + + var classifier = 'bin'; + + console.log(c.bold(' ' + getJava() + ' \\')); + if (npm.jvmci || process.env['JVMCI']) { + classifier = 'bin-jvmci'; + console.log(c.bold(' --module-path=target/compiler \\')); + console.log(c.bold(' -XX:+UnlockExperimentalVMOptions \\')); + console.log(c.bold(' -XX:+EnableJVMCI \\')); + console.log(c.bold(' --upgrade-module-path=target/compiler/compiler.jar \\')); + } + console.log(c.bold(' -jar target/' + (npm.artifactId || npm.name) + '-' + npm.version + '-' + classifier + '.jar')); + console.log(); + }); + } catch (e) { + console.error(c.red.bold(e)); + process.exit(1); + } + }); + program .command('*') .description('Prints this help') diff --git a/es4x-cli/package.json b/es4x-cli/package.json index b4daaddab..c20f155e9 100644 --- a/es4x-cli/package.json +++ b/es4x-cli/package.json @@ -1,6 +1,6 @@ { "name": "es4x-cli", - "version": "0.0.2", + "version": "0.0.3", "readme": "README.md", "description": "Utilities to work with Eclipse Vert.x", "scripts": { diff --git a/examples/empty-project/package.json b/examples/empty-project/package.json index 01529a7b7..2658e6dab 100644 --- a/examples/empty-project/package.json +++ b/examples/empty-project/package.json @@ -4,21 +4,21 @@ "private": true, "description": "A barebones application", "main": "index.js", - "scripts": { - "postinstall": "vertx-scripts init", - "test": "vertx-scripts launcher test -v", - "start": "vertx-scripts launcher run", - "package": "vertx-scripts package", - "repl": "vertx-scripts repl" - }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "vertx-scripts": "latest", - "@vertx/unit": "latest" + "@vertx/unit": "latest", + "es4x-cli": "latest" }, "dependencies": { "@vertx/core": "latest" + }, + "scripts": { + "postinstall": "es4x postinstall", + "start": "es4x launcher run", + "test": "es4x launcher test", + "shell": "es4x shell" } } diff --git a/examples/techempower/pom.xml b/examples/techempower/pom.xml index 0ae511793..5d62f983b 100644 --- a/examples/techempower/pom.xml +++ b/examples/techempower/pom.xml @@ -66,27 +66,27 @@ io.vertx vertx-web-templ-handlebars - 3.5.2 + 3.5.4 io.vertx vertx-core - 3.5.3 + 3.5.4 io.vertx vertx-web - 3.5.3 + 3.5.4 io.vertx vertx-auth-common - 3.5.3 + 3.5.4 io.vertx vertx-web-common - 3.5.3 + 3.5.4 io.reactiverse @@ -96,7 +96,7 @@ io.vertx vertx-unit - 3.5.3 + 3.5.4 test diff --git a/pom.xml b/pom.xml index 49e84801f..265899126 100644 --- a/pom.xml +++ b/pom.xml @@ -13,11 +13,11 @@ io.reactiverse es4x - 0.5.5 + 0.5.6 3.5.4 - 1.0.0-rc7 + 1.0.0-rc9 UTF-8 @@ -108,7 +108,7 @@ provided - org.graalvm + org.graalvm.sdk graal-sdk ${graal.version} provided diff --git a/src/main/java/io/reactiverse/es4x/FatalException.java b/src/main/java/io/reactiverse/es4x/FatalException.java deleted file mode 100644 index 16cbb3966..000000000 --- a/src/main/java/io/reactiverse/es4x/FatalException.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Apache License v2.0 which accompanies this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * The Apache License v2.0 is available at - * http://www.opensource.org/licenses/apache2.0.php - * - * You may elect to redistribute this code under either of these licenses. - */ -package io.reactiverse.es4x; - -import org.graalvm.polyglot.PolyglotException; - -public final class FatalException extends Exception { - public FatalException(PolyglotException cause) { - super(cause); - } -} diff --git a/src/main/java/io/reactiverse/es4x/IncompleteSourceException.java b/src/main/java/io/reactiverse/es4x/IncompleteSourceException.java deleted file mode 100644 index 633312c20..000000000 --- a/src/main/java/io/reactiverse/es4x/IncompleteSourceException.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Apache License v2.0 which accompanies this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * The Apache License v2.0 is available at - * http://www.opensource.org/licenses/apache2.0.php - * - * You may elect to redistribute this code under either of these licenses. - */ -package io.reactiverse.es4x; - -import org.graalvm.polyglot.PolyglotException; - -public final class IncompleteSourceException extends Exception { - public IncompleteSourceException(PolyglotException cause) { - super(cause); - } -} diff --git a/src/main/java/io/reactiverse/es4x/Loader.java b/src/main/java/io/reactiverse/es4x/Loader.java deleted file mode 100644 index 6af3a8537..000000000 --- a/src/main/java/io/reactiverse/es4x/Loader.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Apache License v2.0 which accompanies this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * The Apache License v2.0 is available at - * http://www.opensource.org/licenses/apache2.0.php - * - * You may elect to redistribute this code under either of these licenses. - */ -package io.reactiverse.es4x; - -import io.vertx.core.json.JsonObject; - -public interface Loader { - - String name(); - - void config(final JsonObject config); - - T require(String main); - - T main(String main); - - T worker(String main, String address); - - T eval(String script) throws Exception; - - default T evalLiteral(CharSequence script) throws Exception { - return eval(script.toString()); - } - - boolean hasMember(T thiz, String key); - - T invokeMethod(T thiz, String method, Object... args); - - T invokeFunction(String function, Object... args); - - void put(String name, Object value); - - default void enter() { - } - - default void leave() { - } - - void close(); -} diff --git a/src/main/java/io/reactiverse/es4x/Runtime.java b/src/main/java/io/reactiverse/es4x/Runtime.java index 85368eaea..ee00b2c72 100644 --- a/src/main/java/io/reactiverse/es4x/Runtime.java +++ b/src/main/java/io/reactiverse/es4x/Runtime.java @@ -25,46 +25,70 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; -public interface Runtime { +public interface Runtime { - static Runtime getCurrent() { + static Runtime getCurrent(Vertx vertx) { String rtName = System.getProperty("es4x.engine"); // rt name takes precedence in the choice if (rtName == null) { String vmName = System.getProperty("java.vm.name"); if (vmName != null && vmName.startsWith("GraalVM")) { - rtName = "GraalJS"; + // we're running on a GraalVM JDK + rtName = "graaljs"; } else { - rtName = "Nashorn"; + // it is not GraalVM JDK but if it is JDK11 and the graal components + // are available graaljs can be available too + + try { + double spec = Double.parseDouble(System.getProperty("java.specification.version")); + if (spec < 11) { + rtName = "nashorn"; + } + } catch (NumberFormatException nfe) { + rtName = "nashorn"; + } } + } else { + rtName = rtName.toLowerCase(); } - if (rtName.equalsIgnoreCase("GraalJS")) { - System.setProperty("es4x.engine", "GraalJS"); - return new GraalRuntime(); + if (rtName == null || "graaljs".equals(rtName)) { + try { + System.setProperty("es4x.engine", "graaljs"); + return new GraalRuntime(vertx); + } catch (NoClassDefFoundError | IllegalStateException e) { + // in the case classes are missing, the graal bits are missing + // so fallback to Nashorn + + // we could also have an illegal state when the graal is missing + // the language bits, in that case also try to fallback to nashorn + rtName = "nashorn"; + } } - if (rtName.equalsIgnoreCase("Nashorn")) { - System.setProperty("es4x.engine", "Nashorn"); - return new NashornRuntime(); + if ("nashorn".equals(rtName)) { + System.setProperty("es4x.engine", rtName); + return new NashornRuntime(vertx); } - System.clearProperty("es4x.engine"); + // no nashorn or graal available on the system! throw new RuntimeException("Unsupported runtime: " + rtName); } /** * return the runtime name + * * @return runtime name. */ String name(); /** * Bootstraps a Vert.x instance + * * @param arguments arguments * @return a vertx instance */ - default Vertx vertx(Map arguments) { + static Vertx vertx(Map arguments) { final VertxOptions options = arguments == null ? new VertxOptions() : new VertxOptions(new JsonObject(arguments)); @@ -101,12 +125,110 @@ default Vertx vertx(Map arguments) { } } - Runtime registerCodec(Vertx vertx); + /** + * passes the given configuration to the runtime. + * + * @param config given configuration. + */ + void config(final JsonObject config); + + /** + * Require a module following the commonjs spec + * + * @param module a module + * @return return the module + */ + T require(String module); /** - * Returns a module loader for the given runtime. + * Requires the main module as a commonjs module, the + * module returned will be flagged as a main module. * - * @return loader + * @param main the main module + * @return the module + */ + T main(String main); + + /** + * Loads a JS Worker, meaning it will become a Vert.x Worker. + * + * @param main the main entry script + * @param address the eventbus address + * @return the module + */ + T worker(String main, String address); + + /** + * Evals a given sript string. + * + * @param script string containing code. + * @return returns the evaluation result. + * @throws Exception on error + */ + T eval(String script) throws Exception; + + /** + * Evals a script literal. Script literals are hidden from the + * chrome inspector loaded scripts. + * + * @param script string containing code. + * @return returns the evaluation result. + * @throws Exception on error + */ + default T evalLiteral(CharSequence script) throws Exception { + return eval(script.toString()); + } + + /** + * Performs property lookups on a given evaluated object. + * + * @param thiz the evaluated object + * @param key the key to lookup + * @return the value associated with the key + */ + boolean hasMember(T thiz, String key); + + /** + * Invokes a method on an evaluated object. + * + * @param thiz the evaluated object + * @param method the method name to invoke + * @param args the vararg arguments list + * @return the call result + */ + T invokeMethod(T thiz, String method, Object... args); + + /** + * Invokes function on the global scope. + * + * @param function the function name to invoke + * @param args the vararg arguments list + * @return the call result + */ + T invokeFunction(String function, Object... args); + + /** + * Puts a value to the global scope. + * + * @param name the key to identify the value in the global scope + * @param value the value to store. + */ + void put(String name, Object value); + + /** + * explicitly enter the script engine scope. + */ + default void enter() { + } + + /** + * explicitly leave the script engine scope. + */ + default void leave() { + } + + /** + * close the current runtime and shutdown all the engine related resources. */ - Loader loader(Vertx vertx); + void close(); } diff --git a/src/main/java/io/reactiverse/es4x/ScriptException.java b/src/main/java/io/reactiverse/es4x/ScriptException.java new file mode 100644 index 000000000..6f500bef3 --- /dev/null +++ b/src/main/java/io/reactiverse/es4x/ScriptException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + */ +package io.reactiverse.es4x; + +/** + * Exception wrapper to avoid depend on Graal exception types + * when dealing with VMs running on Nashorn only environments + */ +public class ScriptException extends RuntimeException { + + private final boolean isExit; + private final boolean isIncompleteSource; + + public ScriptException(Throwable other, boolean isIncompleteSource, boolean isExit) { + super(other.getMessage(), other.getCause()); + this.isIncompleteSource = isIncompleteSource; + this.isExit = isExit; + } + + public boolean isIncompleteSource() { + return isIncompleteSource; + } + + public boolean isExit() { + return isExit; + } +} diff --git a/src/main/java/io/reactiverse/es4x/Shell.java b/src/main/java/io/reactiverse/es4x/Shell.java index e0f263201..eb5864bc0 100644 --- a/src/main/java/io/reactiverse/es4x/Shell.java +++ b/src/main/java/io/reactiverse/es4x/Shell.java @@ -15,6 +15,7 @@ */ package io.reactiverse.es4x; +import io.vertx.core.Context; import io.vertx.core.Vertx; import java.io.*; @@ -48,8 +49,6 @@ private static String toCamelCase(String arg) { public static void main(String[] args) { - final Runtime runtime = Runtime.getCurrent(); - final Map options = new HashMap<>(); String script = null; @@ -94,47 +93,101 @@ public static void main(String[] args) { } // create the vertx instance that will boostrap the whole process - final Vertx vertx = runtime.vertx(options); + final Vertx vertx = Runtime.vertx(options); final String main = script; + if (main == null && System.console() == null) { + // invalid state, no script and no console + System.err.println("\u001B[1m\u001B[31mNo Script provided in non interactive shell!\u001B[0m"); + System.exit(1); + } + // move the context to the event loop vertx.runOnContext(v -> { - runtime.registerCodec(vertx); - - final Loader loader = runtime.loader(vertx); + final Runtime runtime = Runtime.getCurrent(vertx); if (main != null) { - loader.main(main); + runtime.main(main); + } else { + new REPL(vertx.getOrCreateContext(), runtime).start(); + } + }); + } + + private static class REPL extends Thread { + + final StringBuilder buffer = new StringBuilder(); + + final Context context; + final Runtime runtime; + + private REPL(Context context, Runtime runtime) { + this.context = context; + this.runtime = runtime; + } + + private synchronized String updateBuffer(String line, boolean resetOrPrepend) { + if (resetOrPrepend) { + buffer.append(line); + final String statement = buffer.toString(); + // reset the buffer + buffer.setLength(0); + return statement; } else { - try (BufferedReader input = new BufferedReader(new InputStreamReader(System.in))) { - final StringBuilder buffer = new StringBuilder(); - for (; ; ) { + // incomplete source, do not handle as error and + // continue appending to the previous buffer + buffer.insert(0, line); + return null; + } + } + + @Override + public void run() { + try (BufferedReader input = new BufferedReader(new InputStreamReader(System.in))) { + System.out.print("> "); + System.out.flush(); + + for (; ; ) { + String line = input.readLine(); + if (line == null) { + break; + } + + final String statement = updateBuffer(line, true); + + // ensure the statement is run on the right context + context.runOnContext(v -> { try { - if (buffer.length() == 0) { - System.out.print("> "); - } else { - System.out.print("| "); + System.out.println("\u001B[1;90m" + runtime.evalLiteral(statement) + "\u001B[0m"); + System.out.print("> "); + System.out.flush(); + } catch (ScriptException t) { + if (t.isIncompleteSource()) { + updateBuffer(statement, false); + return; } - String line = input.readLine(); - if (line == null) { - break; + System.out.println("\u001B[1m\u001B[31m" + t.getMessage() + "\u001B[0m\n"); + + if (t.isExit()) { + // polyglot engine is requesting to exit + // REPL is cancelled, close the loader + try { + runtime.close(); + System.exit(1); + } catch (RuntimeException e) { + // ignore... + } + // force a error code out + System.exit(1); } - buffer.append(line); - - System.out.println("\u001B[1;90m" + loader.evalLiteral(buffer) + "\u001B[0m"); - // reset the buffer - buffer.setLength(0); - } catch (IncompleteSourceException t) { - // incomplete source, do not handle as error and - // continue appending to the previous buffer - } catch (FatalException t) { - // polyglot engine is requesting to exit - break; - } catch (Exception t) { - // reset the buffer as the source is complete - // but there's an error anyway - buffer.setLength(0); + + System.out.print("> "); + System.out.flush(); + } catch (Throwable t) { + String message = null; + String trace = null; + // collect the trace back to a string try (StringWriter sw = new StringWriter()) { PrintWriter pw = new PrintWriter(sw); @@ -142,25 +195,30 @@ public static void main(String[] args) { String sStackTrace = sw.toString(); // stack trace as a string int idx = sStackTrace.indexOf("\n\tat"); if (idx != -1) { - System.out.print("\u001B[1m\u001B[31m" + sStackTrace.substring(0, idx) + "\u001B[0m"); - System.out.println(sStackTrace.substring(idx)); + message = sStackTrace.substring(0, idx); + trace = sStackTrace.substring(idx); } else { - System.out.println(sStackTrace); + trace = sStackTrace; } + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + } + + if (message != null) { + System.out.print("\u001B[1m\u001B[31m" + message + "\u001B[0m"); } + + System.out.println(trace); + System.out.print("> "); + System.out.flush(); } - } - // REPL is cancelled, close the loader - try { - loader.close(); - } catch (RuntimeException e) { - // ignore... - } - } catch (IOException e) { - e.printStackTrace(); - System.exit(1); + }); } + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); } - }); + } } } diff --git a/src/main/java/io/reactiverse/es4x/VerticleFactory.java b/src/main/java/io/reactiverse/es4x/VerticleFactory.java index 20d6a5773..a944d96d8 100644 --- a/src/main/java/io/reactiverse/es4x/VerticleFactory.java +++ b/src/main/java/io/reactiverse/es4x/VerticleFactory.java @@ -22,8 +22,6 @@ public class VerticleFactory implements io.vertx.core.spi.VerticleFactory { - private final Runtime runtime = Runtime.getCurrent(); - private Vertx vertx; @Override @@ -39,15 +37,10 @@ public String prefix() { @Override public Verticle createVerticle(String verticleName, ClassLoader classLoader) { - final Loader engine; + final Runtime runtime; synchronized (this) { - engine = runtime - // now that the vertx instance fully initialized and available, - // register the custom JS codec for the eventbus - .registerCodec(vertx) - // create a new CommonJS loader - .loader(vertx); + runtime = Runtime.getCurrent(vertx); } return new Verticle() { @@ -86,7 +79,7 @@ public void start(Future startFuture) throws Exception { worker = context.isWorkerContext() || context.isMultiThreadedWorkerContext(); // expose config if (context.config() != null) { - engine.config(context.config()); + runtime.config(context.config()); } } else { worker = false; @@ -96,32 +89,32 @@ public void start(Future startFuture) throws Exception { // this can take some time to load so it might block the event loop // this is usually not a issue as it is a one time operation try { - engine.enter(); + runtime.enter(); if (worker) { - self = engine.worker(fsVerticleName, address); + self = runtime.worker(fsVerticleName, address); } else { - self = engine.main(fsVerticleName); + self = runtime.main(fsVerticleName); } } catch (RuntimeException e) { startFuture.fail(e); return; } finally { - engine.leave(); + runtime.leave(); } if (self != null) { if (worker) { // if it is a worker and there is a onmessage handler we need to bind it to the eventbus - if (engine.hasMember(self, "onmessage")) { + if (runtime.hasMember(self, "onmessage")) { try { // if the worker has specified a onmessage function we need to bind it to the eventbus - final Object JSON = engine.eval("JSON"); + final Object JSON = runtime.eval("JSON"); vertx.eventBus().consumer(address + ".out", msg -> { // parse the json back to the engine runtime type - Object json = engine.invokeMethod(JSON, "parse", msg.body()); + Object json = runtime.invokeMethod(JSON, "parse", msg.body()); // deliver it to the handler - engine.invokeMethod(self, "onmessage", json); + runtime.invokeMethod(self, "onmessage", json); }); } catch (RuntimeException e) { startFuture.fail(e); @@ -130,15 +123,15 @@ public void start(Future startFuture) throws Exception { } } else { // if the main module exports 2 function we bind those to the verticle lifecycle - if (engine.hasMember(self, "start")) { + if (runtime.hasMember(self, "start")) { try { - engine.enter(); - engine.invokeMethod(self, "start"); + runtime.enter(); + runtime.invokeMethod(self, "start"); } catch (RuntimeException e) { startFuture.fail(e); return; } finally { - engine.leave(); + runtime.leave(); } } } @@ -151,21 +144,21 @@ public void start(Future startFuture) throws Exception { @Override public void stop(Future stopFuture) { if (self != null) { - if (engine.hasMember(self, "stop")) { + if (runtime.hasMember(self, "stop")) { try { - engine.enter(); - engine.invokeMethod(self, "stop"); + runtime.enter(); + runtime.invokeMethod(self, "stop"); } catch (RuntimeException e) { stopFuture.fail(e); return; } finally { // done! - engine.leave(); + runtime.leave(); } } } // close the loader - engine.close(); + runtime.close(); stopFuture.complete(); } }; diff --git a/src/main/java/io/reactiverse/es4x/impl/graal/GraalLoader.java b/src/main/java/io/reactiverse/es4x/impl/graal/GraalLoader.java deleted file mode 100644 index 8d21c6e48..000000000 --- a/src/main/java/io/reactiverse/es4x/impl/graal/GraalLoader.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Apache License v2.0 which accompanies this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * The Apache License v2.0 is available at - * http://www.opensource.org/licenses/apache2.0.php - * - * You may elect to redistribute this code under either of these licenses. - */ -package io.reactiverse.es4x.impl.graal; - -import io.reactiverse.es4x.FatalException; -import io.reactiverse.es4x.IncompleteSourceException; -import io.reactiverse.es4x.Loader; -import io.vertx.core.Vertx; -import io.vertx.core.json.JsonObject; -import org.graalvm.polyglot.*; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Map; -import java.util.function.Function; - -public class GraalLoader implements Loader { - - private final Context context; - private final Value bindings; - private final Value module; - - public GraalLoader(final Vertx vertx) { - this( - vertx, - // create the default context - Context - .newBuilder("js") - .allowHostAccess(true) - .allowCreateThread(true) - .build() - ); - } - - private static String getCWD() { - // clean up the current working dir - String cwdOverride = System.getProperty("vertx.cwd"); - String cwd; - // are the any overrides? - if (cwdOverride != null) { - cwd = new File(cwdOverride).getAbsolutePath(); - } else { - cwd = System.getProperty("user.dir"); - } - // ensure it's not null - if (cwd == null) { - cwd = ""; - } - - // all paths are unix paths - cwd = cwd.replace('\\', '/'); - // ensure it ends with / - if (cwd.charAt(cwd.length() - 1) != '/') { - cwd += '/'; - } - - // append the required prefix - return "file://" + cwd; - } - - public GraalLoader(final Vertx vertx, Context context) { - - this.context = context; - this.bindings = this.context.getBindings("js"); - - // remove the exit and quit functions - bindings.removeMember("exit"); - bindings.removeMember("quit"); - // add vertx as a global - bindings.putMember("vertx", vertx); - - // clean up the current working dir - final String cwd = getCWD(); - - // override the default load function to allow proper mapping of file for debugging - bindings.putMember("load", new Function() { - @Override - public Value apply(Object value) { - - try { - final Source source; - - if (value instanceof String) { - // a path or url in string format - try { - // try to parse as URL - return apply(new URL((String) value)); - } catch (MalformedURLException murle) { - // on failure fallback to file - return apply(new File((String) value)); - } - } - else if (value instanceof URL) { - // a url - source = Source.newBuilder("js", (URL) value).build(); - } - else if (value instanceof File) { - // a local file - source = Source.newBuilder("js", (File) value).build(); - } - else if (value instanceof Map) { - // a json document - final CharSequence script = (CharSequence) ((Map) value).get("script"); - // might be optional - final CharSequence name = (CharSequence) ((Map) value).get("name"); - - if (name != null && name.length() > 0) { - final URI uri; - if (name.charAt(0) != '/') { - // relative uri - uri = new URI(cwd + name); - } else { - // absolute uri - uri = new URI("file://" + name); - } - - source = Source.newBuilder("js", script, name.toString()).uri(uri).build(); - } else { - source = Source.newBuilder("js", script, "").build(); - } - } else { - throw new RuntimeException("TypeError: cannot load [" + value.getClass() + "]"); - } - - return context.eval(source); - } catch (IOException | URISyntaxException e) { - throw new RuntimeException(e); - } - } - }); - - // load all the polyfills - try { - context.eval(Source.newBuilder("js", Loader.class.getResource("/io/reactiverse/es4x/polyfill/json.js")).build()); - context.eval(Source.newBuilder("js", Loader.class.getResource("/io/reactiverse/es4x/polyfill/global.js")).build()); - context.eval(Source.newBuilder("js", Loader.class.getResource("/io/reactiverse/es4x/polyfill/date.js")).build()); - context.eval(Source.newBuilder("js", Loader.class.getResource("/io/reactiverse/es4x/polyfill/console.js")).build()); - context.eval(Source.newBuilder("js", Loader.class.getResource("/io/reactiverse/es4x/polyfill/promise.js")).build()); - context.eval(Source.newBuilder("js", Loader.class.getResource("/io/reactiverse/es4x/polyfill/worker.js")).build()); - module = context.eval(Source.newBuilder("js", Loader.class.getResource("/io/reactiverse/es4x/jvm-npm.js")).build()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public String name() { - return "GraalJS"; - } - - @Override - public void config(final JsonObject config) { - if (config != null) { - // add config as a global - bindings.putMember("config", config.getMap()); - } - } - - @Override - public Value require(String main) { - return bindings.getMember("require").execute(main); - } - - @Override - public Value main(String main) { - // patch the main path to be a relative path - if (!main.startsWith("./") && !main.startsWith("/")) { - main = "./" + main; - } - // invoke the main script - return invokeMethod(module, "runMain", main); - } - - @Override - public Value worker(String main, String address) { - // invoke the main script - return invokeMethod(module, "runWorker", main, address); - } - - @Override - public Value eval(String script) { - return context.eval("js", script); - } - - @Override - public Value evalLiteral(CharSequence literal) throws IncompleteSourceException, FatalException { - - try { - final Source source = Source - .newBuilder("js", literal, "") - .interactive(true) - .buildLiteral(); - - return context.eval(source); - } catch (PolyglotException e) { - if (e.isIncompleteSource()) { - throw new IncompleteSourceException(e); - } - if (e.isExit()) { - throw new FatalException(e); - } - - throw e; - } - } - - @Override - public boolean hasMember(Value object, String key) { - if (object != null) { - return object.hasMember(key); - } - return false; - } - - @Override - public Value invokeMethod(Value thiz, String method, Object... args) { - Value fn = thiz.getMember(method); - if (fn != null && !fn.isNull()) { - return fn.execute(args); - } - return null; - } - - @Override - public Value invokeFunction(String function, Object... args) { - return context.eval("js", function).execute(args); - } - - @Override - public void put(String name, Object value) { - bindings.putMember(name, value); - } - - @Override - public void close() { - context.close(); - } - - @Override - public void enter() { - context.enter(); - } - - @Override - public void leave() { - context.leave(); - } - - public Engine getEngine() { - return context.getEngine(); - } - - public Value eval(Source source) { - return context.eval(source); - } -} diff --git a/src/main/java/io/reactiverse/es4x/impl/graal/GraalRuntime.java b/src/main/java/io/reactiverse/es4x/impl/graal/GraalRuntime.java index ec66dbdb2..963459563 100644 --- a/src/main/java/io/reactiverse/es4x/impl/graal/GraalRuntime.java +++ b/src/main/java/io/reactiverse/es4x/impl/graal/GraalRuntime.java @@ -15,32 +15,191 @@ */ package io.reactiverse.es4x.impl.graal; -import io.reactiverse.es4x.Loader; import io.reactiverse.es4x.Runtime; +import io.reactiverse.es4x.ScriptException; import io.vertx.core.Vertx; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Source; -import org.graalvm.polyglot.Value; +import io.vertx.core.json.JsonObject; +import org.graalvm.polyglot.*; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Function; public class GraalRuntime implements Runtime { - // Graal AOT config - private static final String EVENTBUS_JSOBJECT_AOT_CLASS; + // this will keep a reference to the type used between the 2 runtimes + // the time should not change across instances so it is safe to assume static + private static final AtomicReference> INTEROP_TYPE = new AtomicReference<>(); static { // if this code is used in a native image avoid the reflection guess game // and hard code the expected class to be a PolyglotMap if (System.getProperty("org.graalvm.nativeimage.imagecode") != null) { - EVENTBUS_JSOBJECT_AOT_CLASS = "com.oracle.truffle.polyglot.PolyglotMap"; + try { + INTEROP_TYPE.set(Class.forName("com.oracle.truffle.polyglot.PolyglotMap")); + } catch (ClassNotFoundException e) { + // swallow the exception and try to get the right class at runtime + } + } + } + + private static final Object lock = new Object(); + // lazily create a polyglot engine + private static Engine engine; + + private final Context context; + private final Value bindings; + private final Value module; + + private static Engine engine() { + synchronized (lock) { + if (engine == null) { + engine = Engine.create(); + } + } + return engine; + } + + public GraalRuntime(final Vertx vertx) { + this( + vertx, + // create the default context + Context + .newBuilder("js") + .allowHostAccess(true) + .allowCreateThread(true) + // by sharing the same engine we allow + // easier debugging and a single remote + // chromeinspector url + .engine(engine()) + .build() + ); + } + + private static String getCWD() { + // clean up the current working dir + String cwdOverride = System.getProperty("vertx.cwd"); + String cwd; + // are the any overrides? + if (cwdOverride != null) { + cwd = new File(cwdOverride).getAbsolutePath(); } else { - EVENTBUS_JSOBJECT_AOT_CLASS = null; + // ensure it's not null + cwd = System.getProperty("user.dir", ""); } + + // all paths are unix paths + cwd = cwd.replace('\\', '/'); + // ensure it ends with / + if (cwd.charAt(cwd.length() - 1) != '/') { + cwd += '/'; + } + + // append the required prefix + return "file://" + cwd; } - private final AtomicReference> interopType = new AtomicReference<>(); + public GraalRuntime(final Vertx vertx, Context context) { + + // register the codec + if (INTEROP_TYPE.get() == null) { + final Consumer callback = value -> INTEROP_TYPE.compareAndSet(null, value.getClass()); + context.eval( + Source.newBuilder("js", "(function (fn) { fn({}); })", "").internal(true).buildLiteral() + ).execute(callback); + } + + // register a default codec to allow JSON messages directly from GraalVM to the JVM world + vertx.eventBus() + .unregisterDefaultCodec(INTEROP_TYPE.get()) + .registerDefaultCodec(INTEROP_TYPE.get(), new JSObjectMessageCodec<>()); + + this.context = context; + this.bindings = this.context.getBindings("js"); + + // remove the exit and quit functions + bindings.removeMember("exit"); + bindings.removeMember("quit"); + // add vertx as a global + bindings.putMember("vertx", vertx); + + // clean up the current working dir + final String cwd = getCWD(); + + // override the default load function to allow proper mapping of file for debugging + bindings.putMember("load", new Function() { + @Override + public Value apply(Object value) { + + try { + final Source source; + + if (value instanceof String) { + // a path or url in string format + try { + // try to parse as URL + return apply(new URL((String) value)); + } catch (MalformedURLException murle) { + // on failure fallback to file + return apply(new File((String) value)); + } + } + else if (value instanceof URL) { + // a url + source = Source.newBuilder("js", (URL) value).build(); + } + else if (value instanceof File) { + // a local file + source = Source.newBuilder("js", (File) value).build(); + } + else if (value instanceof Map) { + // a json document + final CharSequence script = (CharSequence) ((Map) value).get("script"); + // might be optional + final CharSequence name = (CharSequence) ((Map) value).get("name"); + + if (name != null && name.length() > 0) { + final URI uri; + if (name.charAt(0) != '/') { + // relative uri + uri = new URI(cwd + name); + } else { + // absolute uri + uri = new URI("file://" + name); + } + + source = Source.newBuilder("js", script, name.toString()).uri(uri).build(); + } else { + source = Source.newBuilder("js", script, "").build(); + } + } else { + throw new RuntimeException("TypeError: cannot load [" + value.getClass() + "]"); + } + + return context.eval(source); + } catch (IOException | URISyntaxException e) { + throw new RuntimeException(e); + } + } + }); + + // load all the polyfills + context.eval(Source.newBuilder("js", Runtime.class.getResource("/io/reactiverse/es4x/polyfill/json.js")).buildLiteral()); + context.eval(Source.newBuilder("js", Runtime.class.getResource("/io/reactiverse/es4x/polyfill/global.js")).buildLiteral()); + context.eval(Source.newBuilder("js", Runtime.class.getResource("/io/reactiverse/es4x/polyfill/date.js")).buildLiteral()); + context.eval(Source.newBuilder("js", Runtime.class.getResource("/io/reactiverse/es4x/polyfill/console.js")).buildLiteral()); + context.eval(Source.newBuilder("js", Runtime.class.getResource("/io/reactiverse/es4x/polyfill/promise.js")).buildLiteral()); + context.eval(Source.newBuilder("js", Runtime.class.getResource("/io/reactiverse/es4x/polyfill/worker.js")).buildLiteral()); + // keep a reference to module + module = context.eval(Source.newBuilder("js", Runtime.class.getResource("/io/reactiverse/es4x/jvm-npm.js")).buildLiteral()); + } @Override public String name() { @@ -48,57 +207,94 @@ public String name() { } @Override - public Runtime registerCodec(Vertx vertx) { + public void config(final JsonObject config) { + if (config != null) { + // add config as a global + bindings.putMember("config", config.getMap()); + } + } + + @Override + public Value require(String main) { + return bindings.getMember("require").execute(main); + } - try { - if (interopType.get() == null) { - if (EVENTBUS_JSOBJECT_AOT_CLASS != null) { - interopType.set(Class.forName(EVENTBUS_JSOBJECT_AOT_CLASS)); - } else { - final Consumer callback = value -> interopType.set(value.getClass()); - Context ctx; - boolean close; - try { - ctx = Context.getCurrent(); - close = false; - } catch (IllegalStateException e) { - // will create a basic context to lookup the type - ctx = Context - .newBuilder("js") - .allowHostAccess(true) - .build(); - close = true; - } + @Override + public Value main(String main) { + // patch the main path to be a relative path + if (!main.startsWith("./") && !main.startsWith("/")) { + main = "./" + main; + } + // invoke the main script + return invokeMethod(module, "runMain", main); + } - ctx.eval( - Source.newBuilder("js", "(function (fn) { fn({}); })", "").internal(true).buildLiteral() - ).execute(callback); + @Override + public Value worker(String main, String address) { + // invoke the main script + return invokeMethod(module, "runWorker", main, address); + } - if (close) { - // type is acquired, so we can discard this context - ctx.close(true); - } - } - } + @Override + public Value eval(String script) { + return context.eval("js", script); + } - // register a default codec to allow JSON messages directly from GraalVM to the JVM world - vertx.eventBus() - .unregisterDefaultCodec(interopType.get()) - .registerDefaultCodec(interopType.get(), new JSObjectMessageCodec<>()); + @Override + public Value evalLiteral(CharSequence literal) { + final Source source = Source + .newBuilder("js", literal, "") + .interactive(true) + .buildLiteral(); - return this; - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); + try { + return context.eval(source); + } catch (PolyglotException e) { + // in this special case we wrap and hide the polyglot type + // so we can keep a contract outside graal + throw new ScriptException(e, e.isIncompleteSource(), e.isExit()); } } - /** - * Returns a module loader for the given runtime. - * - * @return loader - */ @Override - public Loader loader(Vertx vertx) { - return new GraalLoader(vertx); + public boolean hasMember(Value object, String key) { + if (object != null) { + return object.hasMember(key); + } + return false; + } + + @Override + public Value invokeMethod(Value thiz, String method, Object... args) { + Value fn = thiz.getMember(method); + if (fn != null && !fn.isNull()) { + return fn.execute(args); + } + return null; + } + + @Override + public Value invokeFunction(String function, Object... args) { + return context.eval("js", function).execute(args); + } + + @Override + public void put(String name, Object value) { + bindings.putMember(name, value); + } + + @Override + public void close() { + context.close(); + } + + @Override + public void enter() { + context.enter(); + } + + @Override + public void leave() { + context.leave(); } } diff --git a/src/main/java/io/reactiverse/es4x/impl/nashorn/NashornLoader.java b/src/main/java/io/reactiverse/es4x/impl/nashorn/NashornLoader.java deleted file mode 100644 index 2e5317f69..000000000 --- a/src/main/java/io/reactiverse/es4x/impl/nashorn/NashornLoader.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Apache License v2.0 which accompanies this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * The Apache License v2.0 is available at - * http://www.opensource.org/licenses/apache2.0.php - * - * You may elect to redistribute this code under either of these licenses. - */ -package io.reactiverse.es4x.impl.nashorn; - -import io.reactiverse.es4x.Loader; -import io.vertx.core.Vertx; -import io.vertx.core.json.JsonObject; -import jdk.nashorn.api.scripting.JSObject; -import jdk.nashorn.api.scripting.NashornScriptEngine; - -import javax.script.*; - -public class NashornLoader implements Loader { - - private final NashornScriptEngine engine; - private final JSObject require; - private final Object module; - - public NashornLoader(final Vertx vertx) { - try { - // enable ES6 features - if (System.getProperty("nashorn.args") == null) { - System.setProperty("nashorn.args", "--language=es6"); - } - // create a engine instance - engine = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn"); - - final Bindings engineBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - // remove the exit and quit functions - engineBindings.remove("exit"); - engineBindings.remove("quit"); - - final Bindings globalBindings = new SimpleBindings(); - // add vertx as a global - globalBindings.put("vertx", vertx); - // add the global reference to the bindings - globalBindings.put("global", engine.eval("this")); - - engine.setBindings(globalBindings, ScriptContext.GLOBAL_SCOPE); - - // add polyfills - engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/object.js"); - engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/json.js"); - engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/global.js"); - engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/date.js"); - engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/console.js"); - engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/promise.js"); - engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/worker.js"); - // install the commonjs loader - module = engine.invokeFunction("load", "classpath:io/reactiverse/es4x/jvm-npm.js"); - // get a reference to the require function - require = (JSObject) engine.get("require"); - - } catch (ScriptException | NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - @Override - public String name() { - return "Nashorn"; - } - - @Override - public void config(final JsonObject config) { - final Bindings engineBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - // expose the config as a global - engineBindings.put("config", config != null ? config.getMap() : null); - } - - @Override - public Object require(String main) { - return require.call(null, main); - } - - @Override - public Object main(String main) { - // patch the main path to be a relative path - if (!main.startsWith("./") && !main.startsWith("/")) { - main = "./" + main; - } - // invoke the main script - return invokeMethod(module, "runMain", main); - } - - @Override - public Object worker(String main, String address) { - // invoke the main script - return invokeMethod(module, "runWorker", main, address); - } - - @Override - public Object eval(String script) throws ScriptException { - return engine.eval(script); - } - - @Override - public boolean hasMember(Object thiz, String key) { - if (thiz instanceof JSObject) { - return (((JSObject) thiz).hasMember(key)); - } - return false; - } - - @Override - public Object invokeMethod(Object thiz, String method, Object... args) { - if (thiz instanceof JSObject) { - if (((JSObject) thiz).hasMember(method)) { - Object fn = ((JSObject) thiz).getMember(method); - return ((JSObject) fn).call(thiz, args); - } - } - - return null; - } - - @Override - public Object invokeFunction(String function, Object... args) { - try { - return engine.invokeFunction(function, args); - } catch (ScriptException | NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - @Override - public void put(String name, Object value) { - engine.put(name, value); - } - - @Override - public void close() { - // NO-OP - } -} diff --git a/src/main/java/io/reactiverse/es4x/impl/nashorn/NashornRuntime.java b/src/main/java/io/reactiverse/es4x/impl/nashorn/NashornRuntime.java index 1ad70fc48..de46f9f3c 100644 --- a/src/main/java/io/reactiverse/es4x/impl/nashorn/NashornRuntime.java +++ b/src/main/java/io/reactiverse/es4x/impl/nashorn/NashornRuntime.java @@ -15,36 +15,140 @@ */ package io.reactiverse.es4x.impl.nashorn; -import io.reactiverse.es4x.Loader; import io.reactiverse.es4x.Runtime; import io.vertx.core.Vertx; +import io.vertx.core.json.JsonObject; +import jdk.nashorn.api.scripting.JSObject; +import jdk.nashorn.api.scripting.NashornScriptEngine; import jdk.nashorn.api.scripting.ScriptObjectMirror; +import javax.script.*; + public class NashornRuntime implements Runtime { + private final NashornScriptEngine engine; + private final JSObject require; + private final Object module; + + public NashornRuntime(final Vertx vertx) { + try { + // enable ES6 features + if (System.getProperty("nashorn.args") == null) { + System.setProperty("nashorn.args", "--language=es6"); + } + // create a engine instance + engine = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn"); + + // register a default codec to allow JSON messages directly from GraalVM to the JVM world + vertx.eventBus() + .unregisterDefaultCodec(ScriptObjectMirror.class) + .registerDefaultCodec(ScriptObjectMirror.class, new JSObjectMessageCodec()); + + final Bindings engineBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + // remove the exit and quit functions + engineBindings.remove("exit"); + engineBindings.remove("quit"); + + final Bindings globalBindings = new SimpleBindings(); + // add vertx as a global + globalBindings.put("vertx", vertx); + // add the global reference to the bindings + globalBindings.put("global", engine.eval("this")); + + engine.setBindings(globalBindings, ScriptContext.GLOBAL_SCOPE); + + // add polyfills + engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/object.js"); + engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/json.js"); + engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/global.js"); + engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/date.js"); + engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/console.js"); + engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/promise.js"); + engine.invokeFunction("load", "classpath:io/reactiverse/es4x/polyfill/worker.js"); + // install the commonjs loader + module = engine.invokeFunction("load", "classpath:io/reactiverse/es4x/jvm-npm.js"); + // get a reference to the require function + require = (JSObject) engine.get("require"); + + } catch (ScriptException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + @Override public String name() { return "Nashorn"; } @Override - public Runtime registerCodec(Vertx vertx) { - // register a default codec to allow JSON messages directly from GraalVM to the JVM world - vertx.eventBus() - .unregisterDefaultCodec(ScriptObjectMirror.class) - .registerDefaultCodec(ScriptObjectMirror.class, new JSObjectMessageCodec()); + public void config(final JsonObject config) { + final Bindings engineBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + // expose the config as a global + engineBindings.put("config", config != null ? config.getMap() : null); + } + + @Override + public Object require(String main) { + return require.call(null, main); + } + + @Override + public Object main(String main) { + // patch the main path to be a relative path + if (!main.startsWith("./") && !main.startsWith("/")) { + main = "./" + main; + } + // invoke the main script + return invokeMethod(module, "runMain", main); + } + + @Override + public Object worker(String main, String address) { + // invoke the main script + return invokeMethod(module, "runWorker", main, address); + } + + @Override + public Object eval(String script) throws ScriptException { + return engine.eval(script); + } + + @Override + public boolean hasMember(Object thiz, String key) { + if (thiz instanceof JSObject) { + return (((JSObject) thiz).hasMember(key)); + } + return false; + } - return this; + @Override + public Object invokeMethod(Object thiz, String method, Object... args) { + if (thiz instanceof JSObject) { + if (((JSObject) thiz).hasMember(method)) { + Object fn = ((JSObject) thiz).getMember(method); + return ((JSObject) fn).call(thiz, args); + } + } + + return null; + } + + @Override + public Object invokeFunction(String function, Object... args) { + try { + return engine.invokeFunction(function, args); + } catch (ScriptException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + @Override + public void put(String name, Object value) { + engine.put(name, value); } - /** - * Returns a module loader for the given runtime. - * - * @param vertx the vertx instance - * @return loader - */ @Override - public Loader loader(Vertx vertx) { - return new NashornLoader(vertx); + public void close() { + // NO-OP } } diff --git a/src/main/resources/io/reactiverse/es4x/jvm-npm.js b/src/main/resources/io/reactiverse/es4x/jvm-npm.js index e5d522a10..840334d8e 100644 --- a/src/main/resources/io/reactiverse/es4x/jvm-npm.js +++ b/src/main/resources/io/reactiverse/es4x/jvm-npm.js @@ -342,9 +342,9 @@ function resolveAsFile(id, root, ext) { let uri; if (id.length > 0 && id[0] === '/') { - uri = new URI(normalizeName(id, ext)).normalize(); + uri = new URI('file://' + normalizeName(id, ext)).normalize(); if (!exists(uri)) { - return resolveAsDirectory(id); + return resolveAsDirectory('file://' + id); } } else { uri = new URI(root ? [root, normalizeName(id, ext)].join('/') : normalizeName(id, ext)).normalize(); diff --git a/src/main/resources/io/reactiverse/es4x/polyfill/global.js b/src/main/resources/io/reactiverse/es4x/polyfill/global.js index a436cf5ae..82d820ee8 100644 --- a/src/main/resources/io/reactiverse/es4x/polyfill/global.js +++ b/src/main/resources/io/reactiverse/es4x/polyfill/global.js @@ -70,15 +70,43 @@ // NO-OP }; + var System = Java.type('java.lang.System'); + // process + var jvmLanguageLevel; + var pid = undefined; + + try { + // are we on java > 9 + jvmLanguageLevel = parseInt(ystem.getProperty('java.specification.version'), 10); + } catch (e) { + jvmLanguageLevel = 8; + } - var ManagementFactory = Java.type('java.lang.management.ManagementFactory'); - var System = Java.type('java.lang.System'); - var pid = ManagementFactory.getRuntimeMXBean().getName(); + if (jvmLanguageLevel >= 9) { + // try to use the new pid API + try { + var ProcessHandle = Java.type('java.lang.ProcessHandle'); + pid = ProcessHandle.current().pid(); + } catch (e) { + // ignore... + } + } + + if (jvmLanguageLevel === 8 || pid === undefined) { + // try to use the ManagementFactory MXBean + try { + var ManagementFactory = Java.type('java.lang.management.ManagementFactory'); + var name = ManagementFactory.getRuntimeMXBean().getName(); + pid = parseInt(parseInt(name.substring(0, name.indexOf('@')), 10)) + } catch (e) { + // ignore... + } + } global.process = { env: System.getenv(), - pid: pid.substring(0, pid.indexOf('@')), + pid: pid, engine: System.getProperty('es4x.engine'), exit: function (exitCode) { @@ -100,7 +128,9 @@ stdout: System.out, stderr: System.err, - stdin: System.in + stdin: System.in, + // non standard + properties: System.getProperties(), } })(global || this); diff --git a/src/main/resources/node_modules/util.js b/src/main/resources/node_modules/util.js index 184665c5e..35902f069 100644 --- a/src/main/resources/node_modules/util.js +++ b/src/main/resources/node_modules/util.js @@ -21,7 +21,7 @@ module.exports = { } } else if (typeof obj === 'object') { switch (process.engine) { - case 'Nashorn': + case 'nashorn': return { __noSuchMethod__: function () { var fn = arguments[0]; @@ -39,7 +39,7 @@ module.exports = { }); } }; - case 'GraalJS': + case 'graaljs': var o = {}; for (let m in obj) { // capture the method diff --git a/src/main/resources/vertx.js b/src/main/resources/vertx.js index 049f9ecf7..e6d339127 100644 --- a/src/main/resources/vertx.js +++ b/src/main/resources/vertx.js @@ -60,12 +60,11 @@ load("classpath:io/reactiverse/es4x/polyfill/object.js"); } - // get the runtime for the current environment - const runtime = Java.type('io.reactiverse.es4x.Runtime').getCurrent(); + const Runtime = Java.type('io.reactiverse.es4x.Runtime'); // install the vertx in the global scope - global['vertx'] = runtime.vertx(options); - // install the message codec - runtime.registerCodec(vertx); + global['vertx'] = Runtime.vertx(options); + // get the runtime for the current environment + const runtime = Runtime.getCurrent(vertx); // load polyfills load("classpath:io/reactiverse/es4x/polyfill/json.js"); load("classpath:io/reactiverse/es4x/polyfill/global.js"); diff --git a/src/test/java-9/io/reactiverse/es4x/dynalink/DinalynkTest.java b/src/test/java-9/io/reactiverse/es4x/dynalink/DinalynkTest.java index 790ac5b88..011f3bcce 100644 --- a/src/test/java-9/io/reactiverse/es4x/dynalink/DinalynkTest.java +++ b/src/test/java-9/io/reactiverse/es4x/dynalink/DinalynkTest.java @@ -1,6 +1,5 @@ package io.reactiverse.es4x.dynalink; -import io.reactiverse.es4x.Loader; import io.reactiverse.es4x.Runtime; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.json.JsonObject; @@ -24,13 +23,13 @@ public class DinalynkTest { @Rule public RunTestOnContext rule = new RunTestOnContext(); - private Loader loader; + private Runtime runtime; @Before public void before() { System.setProperty("es4x.engine", "Nashorn"); - loader = Runtime.getCurrent().loader(rule.vertx()); - assumeTrue(loader.name().equalsIgnoreCase("Nashorn")); + runtime = Runtime.getCurrent(rule.vertx()); + assumeTrue(runtime.name().equalsIgnoreCase("Nashorn")); } public static String testJSON(JsonObject o) { @@ -54,7 +53,7 @@ public static String testDate(Date instant) { public void testCasting(TestContext should) throws Exception { final Async test = should.async(); - should.assertEquals("{\n \"foo\" : \"bar\"\n}", loader.eval( + should.assertEquals("{\n \"foo\" : \"bar\"\n}", runtime.eval( "var DynalinkTest = Java.type('io.reactiverse.es4x.dynalink.DinalynkTest');\n" + "DynalinkTest.testJSON({foo: 'bar'});\n")); @@ -65,7 +64,7 @@ public void testCasting(TestContext should) throws Exception { public void testDataObject(TestContext should) throws Exception { final Async test = should.async(); - should.assertEquals(new HttpServerOptions().toJson().encodePrettily(), loader.eval( + should.assertEquals(new HttpServerOptions().toJson().encodePrettily(), runtime.eval( "var DynalinkTest = Java.type('io.reactiverse.es4x.dynalink.DinalynkTest');\n" + "DynalinkTest.testDataObject({foo: 'bar'});\n")); @@ -76,7 +75,7 @@ public void testDataObject(TestContext should) throws Exception { public void testInstant(TestContext should) throws Exception { final Async test = should.async(); - should.assertEquals("OK", loader.eval( + should.assertEquals("OK", runtime.eval( "var DynalinkTest = Java.type('io.reactiverse.es4x.dynalink.DinalynkTest');\n" + "DynalinkTest.testInstant(new Date());\n")); @@ -87,7 +86,7 @@ public void testInstant(TestContext should) throws Exception { public void testDate(TestContext should) throws Exception { final Async test = should.async(); - should.assertEquals("OK", loader.eval( + should.assertEquals("OK", runtime.eval( "var DynalinkTest = Java.type('io.reactiverse.es4x.dynalink.DinalynkTest');\n" + "DynalinkTest.testDate(new Date());\n")); diff --git a/src/test/java/io/reactiverse/es4x/test/AliasTest.java b/src/test/java/io/reactiverse/es4x/test/AliasTest.java index a45aca081..cf26b016a 100644 --- a/src/test/java/io/reactiverse/es4x/test/AliasTest.java +++ b/src/test/java/io/reactiverse/es4x/test/AliasTest.java @@ -1,9 +1,6 @@ package io.reactiverse.es4x.test; -import io.reactiverse.es4x.Loader; import io.reactiverse.es4x.Runtime; -import io.vertx.ext.unit.Async; -import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.RunTestOnContext; import io.vertx.ext.unit.junit.VertxUnitRunnerWithParametersFactory; import org.junit.Before; @@ -27,7 +24,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public AliasTest(String engine) { System.setProperty("es4x.engine", engine); @@ -40,7 +37,7 @@ public AliasTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -48,6 +45,6 @@ public void initialize() { @Test(timeout = 10000) public void testAlias() throws Exception { - loader.eval("require('./alias')"); + runtime.eval("require('./alias')"); } } diff --git a/src/test/java/io/reactiverse/es4x/test/CommonJSCyclicTest.java b/src/test/java/io/reactiverse/es4x/test/CommonJSCyclicTest.java index 405625680..5c41afa69 100644 --- a/src/test/java/io/reactiverse/es4x/test/CommonJSCyclicTest.java +++ b/src/test/java/io/reactiverse/es4x/test/CommonJSCyclicTest.java @@ -1,6 +1,5 @@ package io.reactiverse.es4x.test; -import io.reactiverse.es4x.Loader; import io.reactiverse.es4x.Runtime; import io.vertx.ext.unit.junit.RunTestOnContext; import org.junit.Before; @@ -26,7 +25,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public CommonJSCyclicTest(String engine) { System.setProperty("es4x.engine", engine); @@ -39,7 +38,7 @@ public CommonJSCyclicTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -47,7 +46,7 @@ public void initialize() { @Test public void shouldHaveTheSameSenseOfAnObjectInAllPlaces() { - Object stream = loader.require("./lib/cyclic2/stream.js"); + Object stream = runtime.require("./lib/cyclic2/stream.js"); assertTrue(isFunction(stream)); Object readable = getMember(stream, "Readable"); assertTrue(isFunction(readable)); diff --git a/src/test/java/io/reactiverse/es4x/test/CommonJSGlobalPollutionTest.java b/src/test/java/io/reactiverse/es4x/test/CommonJSGlobalPollutionTest.java index 6a41b54d6..321127dba 100644 --- a/src/test/java/io/reactiverse/es4x/test/CommonJSGlobalPollutionTest.java +++ b/src/test/java/io/reactiverse/es4x/test/CommonJSGlobalPollutionTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.ext.unit.junit.RunTestOnContext; import org.junit.Before; import org.junit.Rule; @@ -24,7 +23,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public CommonJSGlobalPollutionTest(String engine) { System.setProperty("es4x.engine", engine); @@ -37,7 +36,7 @@ public CommonJSGlobalPollutionTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -47,7 +46,7 @@ public void initialize() { public void shouldHaveSideEffects() { try { // this test verifies that the pollution of the global context behaves like on node - loader.require("./pollution/a.js"); + runtime.require("./pollution/a.js"); fail("should throw"); } catch (Exception e) { assertEquals("Error: engine is tainted: b", e.getMessage()); diff --git a/src/test/java/io/reactiverse/es4x/test/CommonJsNODEPATHTest.java b/src/test/java/io/reactiverse/es4x/test/CommonJsNODEPATHTest.java index b8039a7f0..3577ecf73 100644 --- a/src/test/java/io/reactiverse/es4x/test/CommonJsNODEPATHTest.java +++ b/src/test/java/io/reactiverse/es4x/test/CommonJsNODEPATHTest.java @@ -1,6 +1,5 @@ package io.reactiverse.es4x.test; -import io.reactiverse.es4x.Loader; import io.reactiverse.es4x.Runtime; import io.vertx.ext.unit.junit.RunTestOnContext; import org.junit.Before; @@ -25,7 +24,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public CommonJsNODEPATHTest(String engine) { System.setProperty("es4x.engine", engine); @@ -38,7 +37,7 @@ public CommonJsNODEPATHTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -48,8 +47,8 @@ public void initialize() { public void shouldLoadAModuleFromACustomROOT() throws Exception { // this test shows that neither the path.js from the jar root or the node_modules module // is loaded as the node path takes precedence - loader.eval("process.env = { NODE_PATH: '" + "./src/test/resources/dist" + "' }"); - Object mod = loader.require("./path"); + runtime.eval("process.env = { NODE_PATH: '" + "./src/test/resources/dist" + "' }"); + Object mod = runtime.require("./path"); assertEquals("dist/path", getMember(mod, "message", String.class)); } } diff --git a/src/test/java/io/reactiverse/es4x/test/CommonJsNodeModulesTest.java b/src/test/java/io/reactiverse/es4x/test/CommonJsNodeModulesTest.java index a488ffc96..1abb86c32 100644 --- a/src/test/java/io/reactiverse/es4x/test/CommonJsNodeModulesTest.java +++ b/src/test/java/io/reactiverse/es4x/test/CommonJsNodeModulesTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.ext.unit.junit.RunTestOnContext; import org.junit.Before; import org.junit.Rule; @@ -27,7 +26,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public CommonJsNodeModulesTest(String engine) { System.setProperty("es4x.engine", engine); @@ -40,7 +39,7 @@ public CommonJsNodeModulesTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -48,31 +47,31 @@ public void initialize() { @Test public void shouldLoadFileModulesFromTheNode_modulesFolderInCwd() { - Object top = loader.require("./lib/a_package"); + Object top = runtime.require("./lib/a_package"); assertEquals("Hello from a file module", getMember(top, "file_module", String.class)); } @Test public void shouldLoadPackageModulesFromNode_modulesFolder() { - Object top = loader.require("./lib/a_package"); + Object top = runtime.require("./lib/a_package"); assertEquals("Hello from a package module", getMember(getMember(top, "pkg_module"), "pkg", String.class)); } @Test public void shouldFindNode_modulePackagesInTheParentPath() { - Object top = loader.require("./lib/a_package"); + Object top = runtime.require("./lib/a_package"); assertEquals("Hello from a file module", getMember(getMember(top, "pkg_module"), "file", String.class)); } @Test public void shouldFindNode_modulePackagesFromSiblingPath() { - Object top = loader.require("./lib/a_package"); + Object top = runtime.require("./lib/a_package"); assertFalse(getMember(getMember(top, "parent_test"), "parentChanged", Boolean.class)); } @Test public void shouldFindNode_modulePackagesAllTheWayUpAboveCwd() { - Object m = loader.require("root_module"); + Object m = runtime.require("root_module"); assertEquals("You are at the root", getMember(m, "message", String.class)); } } diff --git a/src/test/java/io/reactiverse/es4x/test/CommonJsTest.java b/src/test/java/io/reactiverse/es4x/test/CommonJsTest.java index c89039f83..260666990 100644 --- a/src/test/java/io/reactiverse/es4x/test/CommonJsTest.java +++ b/src/test/java/io/reactiverse/es4x/test/CommonJsTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.ext.unit.junit.RunTestOnContext; import org.junit.Before; import org.junit.Rule; @@ -28,7 +27,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public CommonJsTest(String engine) { System.setProperty("es4x.engine", engine); @@ -41,7 +40,7 @@ public CommonJsTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -50,7 +49,7 @@ public void initialize() { @Test public void shouldThrowAnErrorIfFileCantBeFound() { try { - loader.require("./not_found.js"); + runtime.require("./not_found.js"); fail(); } catch (RuntimeException e) { assertTrue("Error: Module \"./not_found.js\" was not found".equals(e.getMessage()) || "ModuleError: Module \"./not_found.js\" was not found".equals(e.getMessage())); @@ -60,7 +59,7 @@ public void shouldThrowAnErrorIfFileCantBeFound() { @Test public void shouldNotWrapErrorsEncounteredWhenLoadingModule() { try { - loader.require("./lib/throws"); + runtime.require("./lib/throws"); fail(); } catch (RuntimeException e) { assertTrue("ReferenceError: \"bar\" is not defined".equals(e.getMessage()) || "ReferenceError: bar is not defined".equals(e.getMessage())); @@ -69,7 +68,7 @@ public void shouldNotWrapErrorsEncounteredWhenLoadingModule() { @Test public void shouldSupportNestedRequires() { - Object outer = loader.require("./lib/outer"); + Object outer = runtime.require("./lib/outer"); Object quadruple = getMember(outer, "quadruple"); Number n = callAs(outer, quadruple, Number.class, 2); assertEquals(8, n.intValue()); @@ -77,7 +76,7 @@ public void shouldSupportNestedRequires() { @Test public void shouldSupportAnIdWithAnExtension() { - Object outer = loader.require("./lib/outer.js"); + Object outer = runtime.require("./lib/outer.js"); Object quadruple = getMember(outer, "quadruple"); Number n = callAs(outer, quadruple, Number.class, 2); assertEquals(8, n.intValue()); @@ -85,28 +84,28 @@ public void shouldSupportAnIdWithAnExtension() { @Test public void shouldReturnDotJsonFileAsJsonObject() { - Object json = loader.require("./lib/some_data.json"); + Object json = runtime.require("./lib/some_data.json"); assertEquals("This is a JSON file", getMember(json, "description", String.class)); } @Test public void shouldCacheModulesInRequireCache() throws Exception { - Object outer = loader.require("./lib/outer.js"); - Object require = loader.eval("require"); + Object outer = runtime.require("./lib/outer.js"); + Object require = runtime.eval("require"); Object cache = getMember(require, "cache"); String filename = getMember(outer, "filename", String.class); // JS has no concept ot equals() so we cast to String to compare assertEquals(getMember(cache, filename).toString(), outer.toString()); - Object outer2 = loader.require("./lib/outer.js"); + Object outer2 = runtime.require("./lib/outer.js"); // JS has no concept ot equals() so we cast to String to compare assertEquals(outer.toString(), outer2.toString()); } @Test public void shoudlHandleCyclicDependencies() { - Object main = loader.require("./lib/cyclic"); + Object main = runtime.require("./lib/cyclic"); assertEquals("Hello from A", (getMember(getMember(main, "a"), "fromA", String.class))); assertEquals("Hello from B", (getMember(getMember(main, "b"), "fromB", String.class))); } diff --git a/src/test/java/io/reactiverse/es4x/test/CommonJsUserModulesTest.java b/src/test/java/io/reactiverse/es4x/test/CommonJsUserModulesTest.java index 0cb9efcb9..c0167e5a0 100644 --- a/src/test/java/io/reactiverse/es4x/test/CommonJsUserModulesTest.java +++ b/src/test/java/io/reactiverse/es4x/test/CommonJsUserModulesTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.ext.unit.junit.RunTestOnContext; import org.junit.Before; import org.junit.Rule; @@ -25,7 +24,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public CommonJsUserModulesTest(String engine) { System.setProperty("es4x.engine", engine); @@ -38,7 +37,7 @@ public CommonJsUserModulesTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -46,20 +45,20 @@ public void initialize() { @Test public void shouldFindPackageJsonInModuleFolder() { - Object packageJson = loader.require("./lib/other_package"); + Object packageJson = runtime.require("./lib/other_package"); assertEquals("cool ranch", getMember(packageJson, "flavor", String.class)); assertEquals("jar:/lib/other_package/lib/subdir", getMember(packageJson, "subdir", String.class)); } @Test public void shouldLoadPackageJsonMainPropertyEvenIfItIsDirectory() { - Object cheese = loader.require( "./lib/cheese"); + Object cheese = runtime.require( "./lib/cheese"); assertEquals("nacho", getMember(cheese, "flavor", String.class)); } @Test public void shouldFindIndexJsInDirectoryIfNoPackageJsonExists() { - Object packageJson = loader.require("./lib/my_package"); + Object packageJson = runtime.require("./lib/my_package"); assertEquals("Hello!", getMember(packageJson, "data", String.class)); } } diff --git a/src/test/java/io/reactiverse/es4x/test/ConsoleTest.java b/src/test/java/io/reactiverse/es4x/test/ConsoleTest.java index ebafa1fb8..6810cec25 100644 --- a/src/test/java/io/reactiverse/es4x/test/ConsoleTest.java +++ b/src/test/java/io/reactiverse/es4x/test/ConsoleTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.ext.unit.junit.RunTestOnContext; import org.junit.*; import org.junit.runner.RunWith; @@ -21,7 +20,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public ConsoleTest(String engine) { System.setProperty("es4x.engine", engine); @@ -34,7 +33,7 @@ public ConsoleTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -43,43 +42,43 @@ public void initialize() { @Test public void testBasicConsole() throws Exception { // print some stuff - loader.eval("console.log('Hello', 'World', '!')"); + runtime.eval("console.log('Hello', 'World', '!')"); // print some stuff - loader.eval("console.log('J: %j', {key:1})"); + runtime.eval("console.log('J: %j', {key:1})"); } @Test public void testConsole() throws Exception { // print some stuff - loader.eval("console.debug('Hello', 'World', '!')"); - loader.eval("console.info('Hello', 'World', '!')"); - loader.eval("console.log('Hello', 'World', '!')"); - loader.eval("console.warn('Hello', 'World', '!')"); - loader.eval("console.error('Hello', 'World', '!')"); + runtime.eval("console.debug('Hello', 'World', '!')"); + runtime.eval("console.info('Hello', 'World', '!')"); + runtime.eval("console.log('Hello', 'World', '!')"); + runtime.eval("console.warn('Hello', 'World', '!')"); + runtime.eval("console.error('Hello', 'World', '!')"); } @Test public void testConsoleAssert() throws Exception { // print some stuff - loader.eval("console.assert(true, 'Hello1')"); - loader.eval("console.assert(false, 'Hello2')"); - loader.eval("console.assert(0, 'Hello3')"); - loader.eval("console.assert({}, 'Hello4')"); + runtime.eval("console.assert(true, 'Hello1')"); + runtime.eval("console.assert(false, 'Hello2')"); + runtime.eval("console.assert(0, 'Hello3')"); + runtime.eval("console.assert({}, 'Hello4')"); } @Test public void testConsoleTrace() throws Exception { - loader.eval("try { throw new Error('durp!'); } catch (e) { console.trace(e); }\n//@ sourceURL=/index.js"); + runtime.eval("try { throw new Error('durp!'); } catch (e) { console.trace(e); }\n//@ sourceURL=/index.js"); } @Test public void testConsoleCount() throws Exception { - loader.eval("console.count('durp'); console.count('durp'); console.count('durp'); console.count('durp')"); + runtime.eval("console.count('durp'); console.count('durp'); console.count('durp'); console.count('durp')"); } @Test public void testConsoleTime() throws Exception { - loader.eval("console.time('durp'); for (var i = 0; i < 1000; i++); console.timeEnd('durp')"); - loader.eval("console.timeEnd('durp');"); + runtime.eval("console.time('durp'); for (var i = 0; i < 1000; i++); console.timeEnd('durp')"); + runtime.eval("console.timeEnd('durp');"); } } diff --git a/src/test/java/io/reactiverse/es4x/test/EventBusTest.java b/src/test/java/io/reactiverse/es4x/test/EventBusTest.java index 20e0aab4b..96a43a3c7 100644 --- a/src/test/java/io/reactiverse/es4x/test/EventBusTest.java +++ b/src/test/java/io/reactiverse/es4x/test/EventBusTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.ext.unit.Async; @@ -29,7 +28,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public EventBusTest(String engine) { System.setProperty("es4x.engine", engine); @@ -42,13 +41,9 @@ public EventBusTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent() - // install the codec - .registerCodec(rule.vertx()) - // create the loader - .loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); - loader.put("eb", rule.vertx().eventBus()); + runtime.put("eb", rule.vertx().eventBus()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -68,7 +63,7 @@ public void testNativeJSObjectOverEB(TestContext ctx) throws Exception { async.complete(); }); - loader.eval("eb.send('test.address.object', {foo: 'bar'})"); + runtime.eval("eb.send('test.address.object', {foo: 'bar'})"); } @Test(timeout = 10000) @@ -87,6 +82,6 @@ public void testNativeJSArrayOverEB(TestContext ctx) throws Exception { async.complete(); }); - loader.eval("eb.send('test.address.array', ['foo', 'bar'])"); + runtime.eval("eb.send('test.address.array', ['foo', 'bar'])"); } } diff --git a/src/test/java/io/reactiverse/es4x/test/GlobalsTest.java b/src/test/java/io/reactiverse/es4x/test/GlobalsTest.java index 4186fe8eb..040074d28 100644 --- a/src/test/java/io/reactiverse/es4x/test/GlobalsTest.java +++ b/src/test/java/io/reactiverse/es4x/test/GlobalsTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.RunTestOnContext; @@ -27,7 +26,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public GlobalsTest(String engine) { System.setProperty("es4x.engine", engine); @@ -40,7 +39,7 @@ public GlobalsTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -50,8 +49,8 @@ public void initialize() { public void testSetTimeout(TestContext ctx) throws Exception { final Async async = ctx.async(); - loader.put("ctx", ctx); - loader.put("async", async); + runtime.put("ctx", ctx); + runtime.put("async", async); /// @language=JavaScript String script = @@ -60,15 +59,15 @@ public void testSetTimeout(TestContext ctx) throws Exception { "}, 1);"; - loader.eval(script); + runtime.eval(script); } @Test(timeout = 10000) public void testSetTimeout0(TestContext ctx) throws Exception { final Async async = ctx.async(); - loader.put("ctx", ctx); - loader.put("async", async); + runtime.put("ctx", ctx); + runtime.put("async", async); /// @language=JavaScript String script = @@ -77,15 +76,15 @@ public void testSetTimeout0(TestContext ctx) throws Exception { "}, 0);"; - loader.eval(script); + runtime.eval(script); } @Test(timeout = 10000) public void testSetTimeoutWithParams(TestContext ctx) throws Exception { final Async async = ctx.async(); - loader.put("ctx", ctx); - loader.put("async", async); + runtime.put("ctx", ctx); + runtime.put("async", async); /// @language=JavaScript String script = @@ -95,11 +94,11 @@ public void testSetTimeoutWithParams(TestContext ctx) throws Exception { "}, 1, 'durp!');"; - loader.eval(script); + runtime.eval(script); } @Test(timeout = 10000) public void testDateToInstant(TestContext ctx) throws Exception { - loader.eval("console.log(new Date().toInstant());"); + runtime.eval("console.log(new Date().toInstant());"); } } diff --git a/src/test/java/io/reactiverse/es4x/test/JSConsoleTest.java b/src/test/java/io/reactiverse/es4x/test/JSConsoleTest.java index 895fc58f9..27c9e9206 100644 --- a/src/test/java/io/reactiverse/es4x/test/JSConsoleTest.java +++ b/src/test/java/io/reactiverse/es4x/test/JSConsoleTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.ext.unit.junit.RunTestOnContext; import org.junit.Before; import org.junit.Rule; @@ -23,7 +22,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public JSConsoleTest(String engine) { System.setProperty("es4x.engine", engine); @@ -36,7 +35,7 @@ public JSConsoleTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -44,26 +43,26 @@ public void initialize() { @Test public void shouldPrintToStdOut() throws Exception { - loader.eval("console.log('test');"); + runtime.eval("console.log('test');"); } @Test public void shouldPrintToStdOutAFormattedString() throws Exception { - loader.eval("console.log('test %s', JSON.stringify({k:1}));"); + runtime.eval("console.log('test %s', JSON.stringify({k:1}));"); } @Test public void shouldPrintErrorToStdOut() throws Exception { - loader.eval("console.error('test');"); + runtime.eval("console.error('test');"); } @Test public void shouldPrintErrorToStdOutAFormattedString() throws Exception { - loader.eval("console.error('test %s', JSON.stringify({k:1}));"); + runtime.eval("console.error('test %s', JSON.stringify({k:1}));"); } @Test public void throwsTest() throws Exception { - loader.eval("try { throw new Error('Boom!'); } catch (e) { console.trace(e); }"); + runtime.eval("try { throw new Error('Boom!'); } catch (e) { console.trace(e); }"); } } diff --git a/src/test/java/io/reactiverse/es4x/test/JSRuntimeTest.java b/src/test/java/io/reactiverse/es4x/test/JSRuntimeTest.java index a7efd28cb..3bfc1de8a 100644 --- a/src/test/java/io/reactiverse/es4x/test/JSRuntimeTest.java +++ b/src/test/java/io/reactiverse/es4x/test/JSRuntimeTest.java @@ -2,45 +2,18 @@ import io.reactiverse.es4x.Runtime; import io.vertx.core.Vertx; -import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; import static org.junit.Assert.*; -import static org.junit.Assume.assumeTrue; -@RunWith(Parameterized.class) public class JSRuntimeTest { - @Parameterized.Parameters - public static List engines() { - return Arrays.asList("Nashorn", "GraalJS"); - } - - private final String engineName; - private final Runtime runtime; - - - public JSRuntimeTest(String engine) { - System.setProperty("es4x.engine", engine); - engineName = engine; - runtime = Runtime.getCurrent(); - } - - @Before - public void initialize() { - assumeTrue(runtime.name().equalsIgnoreCase(engineName)); - } - @Test public void shouldCreateAVertxInstance() { - Vertx vertx = runtime.vertx(new HashMap<>()); + Vertx vertx = Runtime.vertx(new HashMap<>()); assertNotNull(vertx); assertFalse(vertx.isClustered()); vertx.close(); @@ -50,7 +23,7 @@ public void shouldCreateAVertxInstance() { public void shouldCreateAClusteredVertxInstance() { final Map arguments = new HashMap<>(); arguments.put("clustered", true); - Vertx vertx = runtime.vertx(arguments); + Vertx vertx = Runtime.vertx(arguments); assertNotNull(vertx); assertTrue(vertx.isClustered()); vertx.close(); diff --git a/src/test/java/io/reactiverse/es4x/test/LoggerTest.java b/src/test/java/io/reactiverse/es4x/test/LoggerTest.java index 52c688ad8..f6bb6bd4c 100644 --- a/src/test/java/io/reactiverse/es4x/test/LoggerTest.java +++ b/src/test/java/io/reactiverse/es4x/test/LoggerTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import io.vertx.ext.unit.junit.RunTestOnContext; @@ -27,7 +26,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public LoggerTest(String engine) { System.setProperty("es4x.engine", engine); @@ -40,7 +39,7 @@ public LoggerTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -57,9 +56,9 @@ public void shouldThrowAnErrorIfFileCantBeFound() { @Test public void shouldPrettyPrintException() { - Object module = loader.require("./exp.js"); + Object module = runtime.require("./exp.js"); try { - loader.invokeMethod(module, "a"); + runtime.invokeMethod(module, "a"); } catch (RuntimeException e) { log.error("Error from Script", e); } diff --git a/src/test/java/io/reactiverse/es4x/test/MultithreadTest.java b/src/test/java/io/reactiverse/es4x/test/MultithreadTest.java index 20b4e0c5e..0fe28bfdb 100644 --- a/src/test/java/io/reactiverse/es4x/test/MultithreadTest.java +++ b/src/test/java/io/reactiverse/es4x/test/MultithreadTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.core.DeploymentOptions; import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; @@ -31,6 +30,7 @@ public static List engines() { } private final String engineName; + private Runtime runtime; public MultithreadTest(String engine) { System.setProperty("es4x.engine", engine); @@ -42,9 +42,8 @@ public MultithreadTest(String engine) { @Before public void initialize() { - Loader loader = Runtime.getCurrent().loader(rule.vertx()); - assumeTrue(loader.name().equalsIgnoreCase(engineName)); - loader.close(); + runtime = Runtime.getCurrent(rule.vertx()); + assumeTrue(runtime.name().equalsIgnoreCase(engineName)); } @Test(timeout = 10000) diff --git a/src/test/java/io/reactiverse/es4x/test/NativeJSONTest.java b/src/test/java/io/reactiverse/es4x/test/NativeJSONTest.java index d931eb5da..ac65e8f6b 100644 --- a/src/test/java/io/reactiverse/es4x/test/NativeJSONTest.java +++ b/src/test/java/io/reactiverse/es4x/test/NativeJSONTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.ext.unit.junit.RunTestOnContext; @@ -27,7 +26,7 @@ public static List engines() { return Arrays.asList("Nashorn", "GraalJS"); } - private Loader loader; + private Runtime runtime; private Object JSON; private final String engineName; @@ -43,15 +42,15 @@ public NativeJSONTest(String engine) { @Before public void initialize() throws Exception { try { - loader = Runtime.getCurrent().loader(rule.vertx()); - JSON = loader.eval("JSON"); + runtime = Runtime.getCurrent(rule.vertx()); + JSON = runtime.eval("JSON"); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } } private Object stringify(Object... args) { - Object res = loader.invokeMethod(JSON, "stringify", args); + Object res = runtime.invokeMethod(JSON, "stringify", args); // Graal engine always wraps if (res instanceof Value) { return ((Value) res).asString(); @@ -76,7 +75,7 @@ public void testNativeJsonArray() { @Test public void testOriginalObject() throws Exception { - Object result = loader.eval("JSON.stringify({foo: 'bar'})"); + Object result = runtime.eval("JSON.stringify({foo: 'bar'})"); assertNotNull(result); // Graal engine always wraps if (result instanceof Value) { @@ -87,7 +86,7 @@ public void testOriginalObject() throws Exception { @Test public void testOriginalArray() throws Exception { - Object result = loader.eval("JSON.stringify(['foo', 'bar'])"); + Object result = runtime.eval("JSON.stringify(['foo', 'bar'])"); assertNotNull(result); // Graal engine always wraps if (result instanceof Value) { diff --git a/src/test/java/io/reactiverse/es4x/test/PathsTest.java b/src/test/java/io/reactiverse/es4x/test/PathsTest.java index 9498de2c0..3ddfd42ec 100644 --- a/src/test/java/io/reactiverse/es4x/test/PathsTest.java +++ b/src/test/java/io/reactiverse/es4x/test/PathsTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.RunTestOnContext; @@ -28,7 +27,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public PathsTest(String engine) { System.setProperty("es4x.engine", engine); @@ -41,7 +40,7 @@ public PathsTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -52,8 +51,8 @@ public void testPaths(TestContext ctx) throws Exception { final Async async = ctx.async(); - loader.put("ctx", ctx); - loader.put("async", async); + runtime.put("ctx", ctx); + runtime.put("async", async); final String script = /// @language=JavaScript @@ -62,7 +61,7 @@ public void testPaths(TestContext ctx) throws Exception { "});\n" + "async.complete();"; - loader.eval(script); + runtime.eval(script); } @Test(timeout = 10000) @@ -70,8 +69,8 @@ public void testPathsWin(TestContext ctx) throws Exception { final Async async = ctx.async(); - loader.put("ctx", ctx); - loader.put("async", async); + runtime.put("ctx", ctx); + runtime.put("async", async); final String userDir = System.getProperty("user.dir"); final String userHome = System.getProperty("user.home"); @@ -90,7 +89,7 @@ public void testPathsWin(TestContext ctx) throws Exception { "});\n" + "async.complete();"; - loader.eval(script); + runtime.eval(script); } finally { System.setProperty("os.name", osName); @@ -104,8 +103,8 @@ public void testPathsNodePath(TestContext ctx) throws Exception { final Async async = ctx.async(); - loader.put("ctx", ctx); - loader.put("async", async); + runtime.put("ctx", ctx); + runtime.put("async", async); try { final String script = @@ -116,9 +115,9 @@ public void testPathsNodePath(TestContext ctx) throws Exception { "});\n" + "async.complete();"; - loader.eval(script); + runtime.eval(script); } finally { - loader.eval("delete require.NODE_PATH;"); + runtime.eval("delete require.NODE_PATH;"); } } @@ -127,8 +126,8 @@ public void testPathsNodePathWin(TestContext ctx) throws Exception { final Async async = ctx.async(); - loader.put("ctx", ctx); - loader.put("async", async); + runtime.put("ctx", ctx); + runtime.put("async", async); final String userDir = System.getProperty("user.dir"); final String userHome = System.getProperty("user.home"); @@ -156,12 +155,12 @@ public void testPathsNodePathWin(TestContext ctx) throws Exception { "});\n" + "async.complete();"; - loader.eval(script); + runtime.eval(script); } finally { System.setProperty("os.name", osName); System.setProperty("user.dir", userDir); System.setProperty("user.home", userHome); - loader.eval("delete require.NODE_PATH;"); + runtime.eval("delete require.NODE_PATH;"); } } } diff --git a/src/test/java/io/reactiverse/es4x/test/ProcessTest.java b/src/test/java/io/reactiverse/es4x/test/ProcessTest.java index 9bb87f586..2db629a42 100644 --- a/src/test/java/io/reactiverse/es4x/test/ProcessTest.java +++ b/src/test/java/io/reactiverse/es4x/test/ProcessTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.ext.unit.junit.RunTestOnContext; import org.graalvm.polyglot.Value; import org.junit.Before; @@ -27,7 +26,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public ProcessTest(String engine) { System.setProperty("es4x.engine", engine); @@ -40,7 +39,7 @@ public ProcessTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -48,7 +47,7 @@ public void initialize() { @Test(timeout = 10000) public void testProcessEnv() throws Exception { - Object res = loader.eval("process.env"); + Object res = runtime.eval("process.env"); Map env; if (res instanceof Map) { env = (Map) res; diff --git a/src/test/java/io/reactiverse/es4x/test/ShellTest.java b/src/test/java/io/reactiverse/es4x/test/ShellTest.java new file mode 100644 index 000000000..70b32d6d6 --- /dev/null +++ b/src/test/java/io/reactiverse/es4x/test/ShellTest.java @@ -0,0 +1,67 @@ +package io.reactiverse.es4x.test; + +import org.junit.Test; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +public class ShellTest { + + private static final String CP = System.getProperty("java.class.path"); + private static final String JAVA = new File(System.getProperty("java.home"), "bin/java").getAbsolutePath(); + private static final File CWD = new File(System.getProperty("user.dir")); + + private int runScript(String script, List shellArgs) throws IOException, InterruptedException { + + final List args = new ArrayList<>( + Arrays.asList( + JAVA, + "-cp", + CP, + "io.reactiverse.es4x.Shell")); + + if (script != null) { + final File file = File.createTempFile("ex4x-test", ".js"); + file.deleteOnExit(); + + try (FileOutputStream out = new FileOutputStream(file)) { + out.write(script.getBytes(StandardCharsets.UTF_8)); + } + + args.add(file.getAbsolutePath()); + } + + if (shellArgs != null) { + args.addAll(shellArgs); + } + + return + new ProcessBuilder(args) + .directory(CWD) + .inheritIO() + .start() + .waitFor(); + } + + @Test(timeout = 10000) + public void shouldRunAScript() throws Exception { + assertEquals(0, runScript("process.exit(0);", null)); + } + + @Test(timeout = 10000) + public void shouldFailWhenTheresNoScript() throws Exception { + assertEquals(1, runScript(null, null)); + } + + @Test(timeout = 30000) + public void shouldRunAScriptInClusterMode() throws Exception { + assertEquals(0, runScript("process.exit(0);", Arrays.asList("-clustered"))); + } +} diff --git a/src/test/java/io/reactiverse/es4x/test/StackTraceTest.java b/src/test/java/io/reactiverse/es4x/test/StackTraceTest.java index 3cf92e586..233a7326e 100644 --- a/src/test/java/io/reactiverse/es4x/test/StackTraceTest.java +++ b/src/test/java/io/reactiverse/es4x/test/StackTraceTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.RunTestOnContext; @@ -28,7 +27,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; @Rule public RunTestOnContext rule = new RunTestOnContext(); @@ -41,7 +40,7 @@ public StackTraceTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -51,20 +50,20 @@ public void initialize() { public void shouldGenerateUsefulStackTrace(TestContext should) throws Exception { final Async test = should.async(); // pass the assertion to the engine - loader.put("should", should); - loader.put("test", test); + runtime.put("should", should); + runtime.put("test", test); - loader.eval("require('./stacktraces')"); + runtime.eval("require('./stacktraces')"); } @Test(timeout = 10000) public void shouldGenerateUsefulStackTraceFromJS(TestContext should) throws Exception { final Async test = should.async(); // pass the assertion to the engine - loader.put("should", should); - loader.put("test", test); + runtime.put("should", should); + runtime.put("test", test); - loader.eval("require('./stacktraces/jserror')"); + runtime.eval("require('./stacktraces/jserror')"); } @Test diff --git a/src/test/java/io/reactiverse/es4x/test/UtilTest.java b/src/test/java/io/reactiverse/es4x/test/UtilTest.java index 4816fcbaa..b52843b57 100644 --- a/src/test/java/io/reactiverse/es4x/test/UtilTest.java +++ b/src/test/java/io/reactiverse/es4x/test/UtilTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.RunTestOnContext; @@ -27,7 +26,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public UtilTest(String engine) { System.setProperty("es4x.engine", engine); @@ -40,7 +39,7 @@ public UtilTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -50,8 +49,8 @@ public void initialize() { public void shouldPromisifyAVertxAPI(TestContext should) throws Exception { final Async test = should.async(); // pass the assertion to the engine - loader.put("should", should); - loader.put("test", test); + runtime.put("should", should); + runtime.put("test", test); //language=JavaScript String script = @@ -65,15 +64,15 @@ public void shouldPromisifyAVertxAPI(TestContext should) throws Exception { " should.fail(error);\n" + "});\n"; - loader.eval(script); + runtime.eval(script); } @Test(timeout = 10000) public void shouldPromisifyAJavaScriptAPI(TestContext should) throws Exception { final Async test = should.async(); // pass the assertion to the engine - loader.put("should", should); - loader.put("test", test); + runtime.put("should", should); + runtime.put("test", test); //language=JavaScript String script = @@ -100,6 +99,6 @@ public void shouldPromisifyAJavaScriptAPI(TestContext should) throws Exception { " should.fail(error);\n" + "});\n"; - loader.eval(script); + runtime.eval(script); } } diff --git a/src/test/java/io/reactiverse/es4x/test/WorkerTest.java b/src/test/java/io/reactiverse/es4x/test/WorkerTest.java index 492dc3493..9edddfedf 100644 --- a/src/test/java/io/reactiverse/es4x/test/WorkerTest.java +++ b/src/test/java/io/reactiverse/es4x/test/WorkerTest.java @@ -1,7 +1,6 @@ package io.reactiverse.es4x.test; import io.reactiverse.es4x.Runtime; -import io.reactiverse.es4x.Loader; import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.RunTestOnContext; @@ -27,7 +26,7 @@ public static List engines() { } private final String engineName; - private Loader loader; + private Runtime runtime; public WorkerTest(String engine) { System.setProperty("es4x.engine", engine); @@ -40,7 +39,7 @@ public WorkerTest(String engine) { @Before public void initialize() { try { - loader = Runtime.getCurrent().loader(rule.vertx()); + runtime = Runtime.getCurrent(rule.vertx()); } catch (IllegalStateException e) { assumeTrue(engineName + " is not available", false); } @@ -50,8 +49,8 @@ public void initialize() { public void testWorkerLoading(TestContext ctx) throws Exception { final Async async = ctx.async(); - loader.put("ctx", ctx); - loader.put("async", async); + runtime.put("ctx", ctx); + runtime.put("async", async); // @language=JavaScript String script = @@ -65,6 +64,6 @@ public void testWorkerLoading(TestContext ctx) throws Exception { "worker.postMessage({data: [2, 3]});\n" + "});\n"; - loader.eval(script); + runtime.eval(script); } }