8168615: JShell API: jdk.jshell.spi should be a pluggable ServiceLoader SPI
Reviewed-by: jlahoda
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Wed Dec 21 20:14:39 2016 -0800
@@ -197,6 +197,7 @@
public boolean testPrompt = false;
private String cmdlineClasspath = null;
private String startup = null;
+ private String executionControlSpec = null;
private EditorSetting editor = BUILT_IN_EDITOR;
private static final String[] EDITOR_ENV_VARS = new String[] {
@@ -546,6 +547,7 @@
OptionSpec<String> st = parser.accepts("startup").withRequiredArg();
parser.acceptsAll(asList("n", "no-startup"));
OptionSpec<String> fb = parser.accepts("feedback").withRequiredArg();
+ OptionSpec<String> ec = parser.accepts("execution").withRequiredArg();
parser.accepts("q");
parser.accepts("s");
parser.accepts("v");
@@ -648,6 +650,9 @@
remoteVMOptions.add("--add-modules");
remoteVMOptions.addAll(options.valuesOf(amods));
}
+ if (options.has(ec)) {
+ executionControlSpec = options.valueOf(ec);
+ }
if (options.has(addExports)) {
List<String> exports = options.valuesOf(addExports).stream()
@@ -718,18 +723,21 @@
// Reset the replayable history, saving the old for restore
replayableHistoryPrevious = replayableHistory;
replayableHistory = new ArrayList<>();
-
- state = JShell.builder()
+ JShell.Builder builder =
+ JShell.builder()
.in(userin)
.out(userout)
.err(usererr)
- .tempVariableNameGenerator(()-> "$" + currentNameSpace.tidNext())
+ .tempVariableNameGenerator(() -> "$" + currentNameSpace.tidNext())
.idGenerator((sn, i) -> (currentNameSpace == startNamespace || state.status(sn).isActive())
? currentNameSpace.tid(sn)
: errorNamespace.tid(sn))
.remoteVMOptions(remoteVMOptions.stream().toArray(String[]::new))
- .compilerOptions(compilerOptions.stream().toArray(String[]::new))
- .build();
+ .compilerOptions(compilerOptions.stream().toArray(String[]::new));
+ if (executionControlSpec != null) {
+ builder.executionEngine(executionControlSpec);
+ }
+ state = builder.build();
shutdownSubscription = state.onShutdown((JShell deadState) -> {
if (deadState == state) {
hardmsg("jshell.msg.terminated");
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties Wed Dec 21 20:14:39 2016 -0800
@@ -169,12 +169,12 @@
\ --module-path <path> Specify where to find application modules\n\
\ --add-modules <module>(,<module>)*\n\
\ Specify modules to resolve, or all modules on the\n\
-\ module path if <module> is ALL-MODULE-PATHs\n\
+\ module path if <module> is ALL-MODULE-PATHs\n\
\ --startup <file> One run replacement for the start-up definitions\n\
\ --no-startup Do not run the start-up definitions\n\
\ --feedback <mode> Specify the initial feedback mode. The mode may be\n\
-\ predefined (silent, concise, normal, or verbose) or\n\
-\ previously user-defined\n\
+\ predefined (silent, concise, normal, or verbose) or\n\
+\ previously user-defined\n\
\ -q Quiet feedback. Same as: --feedback concise\n\
\ -s Really quiet feedback. Same as: --feedback silent\n\
\ -v Verbose feedback. Same as: --feedback verbose\n\
@@ -189,6 +189,10 @@
\ -X Print help on non-standard options\n
help.usage.x = \
\ --add-exports <module>/<package> Export specified module-private package to snippets\n\
+\ --execution <spec> Specify an alternate execution engine.\n\
+\ Where <spec> is an ExecutionControl spec.\n\
+\ See the documentation of the package\n\
+\ jdk.jshell.spi for the syntax of the spec\n\
\ \n\
\These options are non-standard and subject to change without notice.\n
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java Wed Dec 21 20:14:39 2016 -0800
@@ -48,11 +48,10 @@
import java.util.stream.Stream;
import jdk.internal.jshell.debug.InternalDebugControl;
import jdk.jshell.Snippet.Status;
-import jdk.jshell.execution.JdiDefaultExecutionControl;
import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
+import jdk.jshell.spi.ExecutionControlProvider;
import jdk.jshell.spi.ExecutionEnv;
-import static jdk.jshell.execution.Util.failOverExecutionControlGenerator;
import static jdk.jshell.Util.expunge;
/**
@@ -116,15 +115,20 @@
this.idGenerator = b.idGenerator;
this.extraRemoteVMOptions = b.extraRemoteVMOptions;
this.extraCompilerOptions = b.extraCompilerOptions;
- ExecutionControl.Generator executionControlGenerator = b.executionControlGenerator==null
- ? failOverExecutionControlGenerator(
- JdiDefaultExecutionControl.listen(InetAddress.getLoopbackAddress().getHostAddress()),
- JdiDefaultExecutionControl.launch(),
- JdiDefaultExecutionControl.listen(null)
- )
- : b.executionControlGenerator;
try {
- executionControl = executionControlGenerator.generate(new ExecutionEnvImpl());
+ if (b.executionControlProvider != null) {
+ executionControl = b.executionControlProvider.generate(new ExecutionEnvImpl(),
+ b.executionControlParameters == null
+ ? b.executionControlProvider.defaultParameters()
+ : b.executionControlParameters);
+ } else {
+ String loopback = InetAddress.getLoopbackAddress().getHostAddress();
+ String spec = b.executionControlSpec == null
+ ? "failover:0(jdi:hostname(" + loopback + ")),"
+ + "1(jdi:launch(true)), 2(jdi)"
+ : b.executionControlSpec;
+ executionControl = ExecutionControl.generate(new ExecutionEnvImpl(), spec);
+ }
} catch (Throwable ex) {
throw new IllegalStateException("Launching JShell execution engine threw: " + ex.getMessage(), ex);
}
@@ -164,7 +168,9 @@
BiFunction<Snippet, Integer, String> idGenerator = null;
List<String> extraRemoteVMOptions = new ArrayList<>();
List<String> extraCompilerOptions = new ArrayList<>();
- ExecutionControl.Generator executionControlGenerator;
+ ExecutionControlProvider executionControlProvider;
+ Map<String,String> executionControlParameters;
+ String executionControlSpec;
Builder() { }
@@ -322,14 +328,39 @@
/**
* Sets the custom engine for execution. Snippet execution will be
- * provided by the specified {@link ExecutionControl} instance.
+ * provided by the {@link ExecutionControl} instance selected by the
+ * specified execution control spec.
+ * Use, at most, one of these overloaded {@code executionEngine} builder
+ * methods.
*
- * @param executionControlGenerator the execution engine generator
+ * @param executionControlSpec the execution control spec,
+ * which is documented in the {@link jdk.jshell.spi}
+ * package documentation.
* @return the {@code Builder} instance (for use in chained
* initialization)
*/
- public Builder executionEngine(ExecutionControl.Generator executionControlGenerator) {
- this.executionControlGenerator = executionControlGenerator;
+ public Builder executionEngine(String executionControlSpec) {
+ this.executionControlSpec = executionControlSpec;
+ return this;
+ }
+
+ /**
+ * Sets the custom engine for execution. Snippet execution will be
+ * provided by the specified {@link ExecutionControl} instance.
+ * Use, at most, one of these overloaded {@code executionEngine} builder
+ * methods.
+ *
+ * @param executionControlProvider the provider to supply the execution
+ * engine
+ * @param executionControlParameters the parameters to the provider, or
+ * {@code null} for default parameters
+ * @return the {@code Builder} instance (for use in chained
+ * initialization)
+ */
+ public Builder executionEngine(ExecutionControlProvider executionControlProvider,
+ Map<String,String> executionControlParameters) {
+ this.executionControlProvider = executionControlProvider;
+ this.executionControlParameters = executionControlParameters;
return this;
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/FailOverExecutionControlProvider.java Wed Dec 21 20:14:39 2016 -0800
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jshell.execution;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionControlProvider;
+import jdk.jshell.spi.ExecutionEnv;
+
+/**
+ * Tries other providers in sequence until one works.
+ */
+public class FailOverExecutionControlProvider implements ExecutionControlProvider{
+
+ private Logger logger = null;
+
+ /**
+ * Create an instance. The instance can be used to start and return an
+ * {@link ExecutionControl} instance by attempting to start a series of
+ * {@code ExecutionControl} specs, until one is successful.
+ *
+ * @see #generate(jdk.jshell.spi.ExecutionEnv, java.util.Map)
+ */
+ public FailOverExecutionControlProvider() {
+ }
+
+ /**
+ * The unique name of this {@code ExecutionControlProvider}.
+ *
+ * @return "failover"
+ */
+ @Override
+ public String name() {
+ return "failover";
+ }
+
+ /**
+ * Create and return the default parameter map for this
+ * {@code ExecutionControlProvider}. There are ten parameters, "0" through
+ * "9", their values are {@code ExecutionControlProvider} specification
+ * strings, or empty string.
+ *
+ * @return a default parameter map
+ */
+ @Override
+ public Map<String, String> defaultParameters() {
+ Map<String, String> dp = new HashMap<>();
+ dp.put("0", "jdi");
+ for (int i = 1; i <= 9; ++i) {
+ dp.put("" + i, "");
+ }
+ return dp;
+ }
+
+ /**
+ * Create and return a locally executing {@code ExecutionControl} instance.
+ * At least one parameter should have a spec.
+ *
+ * @param env the execution environment, provided by JShell
+ * @param parameters the modified parameter map.
+ * @return the execution engine
+ * @throws Throwable if all the given providers fail, the exception that
+ * occurred on the first attempt to create the execution engine.
+ */
+ @Override
+ public ExecutionControl generate(ExecutionEnv env, Map<String, String> parameters)
+ throws Throwable {
+ Throwable thrown = null;
+ for (int i = 0; i <= 9; ++i) {
+ String param = parameters.get("" + i);
+ if (param != null && !param.isEmpty()) {
+ try {
+ ExecutionControl ec = ExecutionControl.generate(env, param);
+ logger().finest(
+ String.format("FailOverExecutionControlProvider: Success %s -- %d = %s\n",
+ name(), i, param));
+ return ec;
+ } catch (Throwable ex) {
+ logger().warning(
+ String.format("FailOverExecutionControlProvider: Failure %s -- %d = %s -- %s\n",
+ name(), i, param, ex.toString()));
+ StringWriter writer = new StringWriter();
+ PrintWriter log = new PrintWriter(writer);
+ log.println("FailOverExecutionControlProvider:");
+ ex.printStackTrace(log);
+ logger().fine(log.toString());
+ // only care about the first, and only if they all fail
+ if (thrown == null) {
+ thrown = ex;
+ }
+ }
+ }
+
+ }
+ logger().severe("FailOverExecutionControlProvider: Terminating, failovers exhausted");
+ if (thrown == null) {
+ throw new IllegalArgumentException("All least one parameter must be set to a provider.");
+ }
+ throw thrown;
+ }
+
+ private Logger logger() {
+ if (logger == null) {
+ logger = Logger.getLogger("jdk.jshell.execution");
+ logger.setLevel(Level.ALL);
+ }
+ return logger;
+ }
+
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java Wed Dec 21 20:14:39 2016 -0800
@@ -63,77 +63,15 @@
*/
public class JdiDefaultExecutionControl extends JdiExecutionControl {
- /**
- * Default time-out expressed in milliseconds.
- */
- private static final int DEFAULT_TIMEOUT = 5000;
-
private VirtualMachine vm;
private Process process;
+ private final String remoteAgent;
private final Object STOP_LOCK = new Object();
private boolean userCodeRunning = false;
/**
* Creates an ExecutionControl instance based on a JDI
- * {@code LaunchingConnector}. Same as
- * {@code JdiDefaultExecutionControl.create(defaultRemoteAgent(), true, null, defaultTimeout())}.
- *
- * @return the generator
- */
- public static ExecutionControl.Generator launch() {
- return create(defaultRemoteAgent(), true, null, defaultTimeout());
- }
-
- /**
- * Creates an ExecutionControl instance based on a JDI
- * {@code ListeningConnector}. Same as
- * {@code JdiDefaultExecutionControl.create(defaultRemoteAgent(), false, host, defaultTimeout())}.
- *
- * @param host explicit hostname to use, if null use discovered
- * hostname, applies to listening only (!isLaunch)
- * @return the generator
- */
- public static ExecutionControl.Generator listen(String host) {
- return create(defaultRemoteAgent(), false, host, defaultTimeout());
- }
-
- /**
- * Creates a JDI based ExecutionControl instance.
- *
- * @param remoteAgent the remote agent to launch
- * @param isLaunch does JDI do the launch? That is, LaunchingConnector,
- * otherwise we start explicitly and use ListeningConnector
- * @param host explicit hostname to use, if null use discovered
- * hostname, applies to listening only (!isLaunch)
- * @param timeout the start-up time-out in milliseconds
- * @return the generator
- */
- public static ExecutionControl.Generator create(String remoteAgent,
- boolean isLaunch, String host, int timeout) {
- return env -> create(env, remoteAgent, isLaunch, host, timeout);
- }
-
- /**
- * Default remote agent.
- *
- * @return the name of the standard remote agent
- */
- public static String defaultRemoteAgent() {
- return RemoteExecutionControl.class.getName();
- }
-
- /**
- * Default remote connection time-out
- *
- * @return time to wait for connection before failing, expressed in milliseconds.
- */
- public static int defaultTimeout() {
- return DEFAULT_TIMEOUT;
- }
-
- /**
- * Creates an ExecutionControl instance based on a JDI
* {@code ListeningConnector} or {@code LaunchingConnector}.
*
* Initialize JDI and use it to launch the remote JVM. Set-up a socket for
@@ -150,7 +88,7 @@
* @return the channel
* @throws IOException if there are errors in set-up
*/
- private static ExecutionControl create(ExecutionEnv env, String remoteAgent,
+ static ExecutionControl create(ExecutionEnv env, String remoteAgent,
boolean isLaunch, String host, int timeout) throws IOException {
try (final ServerSocket listener = new ServerSocket(0, 1, InetAddress.getLoopbackAddress())) {
// timeout on I/O-socket
@@ -181,7 +119,8 @@
outputs.put("err", env.userErr());
Map<String, InputStream> input = new HashMap<>();
input.put("in", env.userIn());
- return remoteInputOutput(socket.getInputStream(), out, outputs, input, (objIn, objOut) -> new JdiDefaultExecutionControl(objOut, objIn, vm, process, deathListeners));
+ return remoteInputOutput(socket.getInputStream(), out, outputs, input,
+ (objIn, objOut) -> new JdiDefaultExecutionControl(objOut, objIn, vm, process, remoteAgent, deathListeners));
}
}
@@ -192,10 +131,12 @@
* @param cmdin the input for responses
*/
private JdiDefaultExecutionControl(ObjectOutput cmdout, ObjectInput cmdin,
- VirtualMachine vm, Process process, List<Consumer<String>> deathListeners) {
+ VirtualMachine vm, Process process, String remoteAgent,
+ List<Consumer<String>> deathListeners) {
super(cmdout, cmdin);
this.vm = vm;
this.process = process;
+ this.remoteAgent = remoteAgent;
deathListeners.add(s -> disposeVM());
}
@@ -237,7 +178,7 @@
for (ThreadReference thread : vm().allThreads()) {
// could also tag the thread (e.g. using name), to find it easier
for (StackFrame frame : thread.frames()) {
- if (defaultRemoteAgent().equals(frame.location().declaringType().name()) &&
+ if (remoteAgent.equals(frame.location().declaringType().name()) &&
( "invoke".equals(frame.location().method().name())
|| "varValue".equals(frame.location().method().name()))) {
ObjectReference thiz = frame.thisObject();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiExecutionControlProvider.java Wed Dec 21 20:14:39 2016 -0800
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jshell.execution;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionControlProvider;
+import jdk.jshell.spi.ExecutionEnv;
+
+/**
+ * A provider of remote JDI-controlled execution engines.
+ * @author Robert Field
+ */
+public class JdiExecutionControlProvider implements ExecutionControlProvider {
+
+ /**
+ * The remote agent to launch.
+ */
+ public static final String PARAM_REMOTE_AGENT = "remoteAgent";
+
+ /**
+ * Milliseconds before connect timeout.
+ */
+ public static final String PARAM_TIMEOUT = "timeout";
+
+ /**
+ * The local hostname to connect to.
+ */
+ public static final String PARAM_HOST_NAME = "hostname";
+
+ /**
+ * Should JDI-controlled launching be used?
+ */
+ public static final String PARAM_LAUNCH = "launch";
+
+ /**
+ * Default time-out expressed in milliseconds.
+ */
+ private static final int DEFAULT_TIMEOUT = 5000;
+
+ /**
+ * Create an instance. An instance can be used to
+ * {@linkplain #generate generate} an {@link ExecutionControl} instance
+ * that uses the Java Debug Interface as part of the control of a remote
+ * process.
+ */
+ public JdiExecutionControlProvider() {
+ }
+
+ /**
+ * The unique name of this {@code ExecutionControlProvider}.
+ *
+ * @return "jdi"
+ */
+ @Override
+ public String name() {
+ return "jdi";
+ }
+
+ /**
+ * Create and return the default parameter map for this
+ * {@code ExecutionControlProvider}. The map can optionally be modified;
+ * Modified or unmodified it can be passed to
+ * {@link #generate(jdk.jshell.spi.ExecutionEnv, java.util.Map) }.
+ * <table summary="Parameters">
+ * <tr>
+ * <th>Parameter</th>
+ * <th>Description</th>
+ * <th>Constant Field</th>
+ * </tr>
+ * <tr>
+ * <td>remoteAgent</td>
+ * <td>the remote agent to launch</td>
+ * <td>{@link #PARAM_REMOTE_AGENT}</td>
+ * </tr>
+ * <tr>
+ * <td>timeout</td>
+ * <td>milliseconds before connect timeout</td>
+ * <td>{@link #PARAM_TIMEOUT}</td>
+ * </tr>
+ * <tr>
+ * <td>launch</td>
+ * <td>"true" for JDI controlled launch</td>
+ * <td>{@link #PARAM_LAUNCH}</td>
+ * </tr>
+ * <tr>
+ * <td>hostname</td>
+ * <td>connect to the named of the local host ("" for discovered)</td>
+ * <td>{@link #PARAM_HOST_NAME}</td>
+ * </tr>
+ * </table>
+ *
+ * @return the default parameter map
+ */
+ @Override
+ public Map<String, String> defaultParameters() {
+ Map<String, String> dp = new HashMap<>();
+ dp.put(PARAM_REMOTE_AGENT, RemoteExecutionControl.class.getName());
+ dp.put(PARAM_TIMEOUT, "" + DEFAULT_TIMEOUT);
+ dp.put(PARAM_HOST_NAME, "");
+ dp.put(PARAM_LAUNCH, "false");
+ return dp;
+ }
+
+ @Override
+ public ExecutionControl generate(ExecutionEnv env, Map<String, String> parameters)
+ throws IOException {
+ Map<String, String> dp = defaultParameters();
+ if (parameters == null) {
+ parameters = dp;
+ }
+ String remoteAgent = parameters.getOrDefault(PARAM_REMOTE_AGENT, dp.get(PARAM_REMOTE_AGENT));
+ int timeout = Integer.parseUnsignedInt(
+ parameters.getOrDefault(PARAM_TIMEOUT, dp.get(PARAM_TIMEOUT)));
+ String host = parameters.getOrDefault(PARAM_HOST_NAME, dp.get(PARAM_HOST_NAME));
+ String sIsLaunch = parameters.getOrDefault(PARAM_LAUNCH, dp.get(PARAM_LAUNCH)).toLowerCase(Locale.ROOT);
+ boolean isLaunch = sIsLaunch.length() > 0
+ && ("true".startsWith(sIsLaunch) || "yes".startsWith(sIsLaunch));
+ return JdiDefaultExecutionControl.create(env, remoteAgent, isLaunch, host, timeout);
+ }
+
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControl.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControl.java Wed Dec 21 20:14:39 2016 -0800
@@ -27,7 +27,6 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicReference;
-import jdk.jshell.spi.ExecutionControl;
/**
* An implementation of {@link jdk.jshell.spi.ExecutionControl} which executes
@@ -42,15 +41,6 @@
private ThreadGroup execThreadGroup;
/**
- * Creates a local ExecutionControl instance.
- *
- * @return the generator
- */
- public static ExecutionControl.Generator create() {
- return env -> new LocalExecutionControl();
- }
-
- /**
* Creates an instance, delegating loader operations to the specified
* delegate.
*
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControlProvider.java Wed Dec 21 20:14:39 2016 -0800
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jshell.execution;
+
+import java.util.Map;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionControlProvider;
+import jdk.jshell.spi.ExecutionEnv;
+
+/**
+ * A provider of execution engines which run in the same process as JShell.
+ * @author Robert Field
+ */
+public class LocalExecutionControlProvider implements ExecutionControlProvider{
+
+ /**
+ * Create an instance. An instance can be used to
+ * {@linkplain #generate generate} an {@link ExecutionControl} instance
+ * that executes code in the same process.
+ */
+ public LocalExecutionControlProvider() {
+ }
+
+ /**
+ * The unique name of this {@code ExecutionControlProvider}.
+ *
+ * @return "local"
+ */
+ @Override
+ public String name() {
+ return "local";
+ }
+
+ /**
+ * Create and return the default parameter map for
+ * {@code LocalExecutionControlProvider}.
+ * {@code LocalExecutionControlProvider} has no parameters.
+ *
+ * @return an empty parameter map
+ */
+ @Override
+ public Map<String,String> defaultParameters() {
+ return ExecutionControlProvider.super.defaultParameters();
+ }
+
+ /**
+ * Create and return a locally executing {@code ExecutionControl} instance.
+ *
+ * @param env the execution environment, provided by JShell
+ * @param parameters the {@linkplain #defaultParameters() default} or
+ * modified parameter map.
+ * @return the execution engine
+ */
+ @Override
+ public ExecutionControl generate(ExecutionEnv env, Map<String, String> parameters) {
+ return new LocalExecutionControl();
+ }
+
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java Wed Dec 21 20:14:39 2016 -0800
@@ -64,36 +64,6 @@
private Util() {}
/**
- * Create a composite {@link ExecutionControl.Generator} instance that, when
- * generating, will try each specified generator until successfully creating
- * an {@link ExecutionControl} instance, or, if all fail, will re-throw the
- * first exception.
- *
- * @param gec0 the first instance to try
- * @param gecs the second through Nth instance to try
- * @return the fail-over generator
- */
- public static ExecutionControl.Generator failOverExecutionControlGenerator(
- ExecutionControl.Generator gec0, ExecutionControl.Generator... gecs) {
- return (ExecutionEnv env) -> {
- Throwable thrown;
- try {
- return gec0.generate(env);
- } catch (Throwable ex) {
- thrown = ex;
- }
- for (ExecutionControl.Generator gec : gecs) {
- try {
- return gec.generate(env);
- } catch (Throwable ignore) {
- // only care about the first, and only if they all fail
- }
- }
- throw thrown;
- };
- }
-
- /**
* Forward commands from the input to the specified {@link ExecutionControl}
* instance, then responses back on the output.
* @param ec the direct instance of {@link ExecutionControl} to process commands
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControl.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControl.java Wed Dec 21 20:14:39 2016 -0800
@@ -25,6 +25,11 @@
package jdk.jshell.spi;
import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.Set;
/**
* This interface specifies the functionality that must provided to implement a
@@ -40,31 +45,10 @@
* <p>
* Methods defined in this interface should only be called by the core JShell
* implementation.
- * <p>
- * To install an {@code ExecutionControl}, its {@code Generator} is passed to
- * {@link jdk.jshell.JShell.Builder#executionEngine(ExecutionControl.Generator) }.
*/
public interface ExecutionControl extends AutoCloseable {
/**
- * Defines a functional interface for creating {@link ExecutionControl}
- * instances.
- */
- @FunctionalInterface
- public interface Generator {
-
- /**
- * Generates an execution engine, given an execution environment.
- *
- * @param env the context in which the {@link ExecutionControl} is to
- * be created
- * @return the created instance
- * @throws Throwable if problems occurred
- */
- ExecutionControl generate(ExecutionEnv env) throws Throwable;
- }
-
- /**
* Attempts to load new classes.
*
* @param cbcs the class name and bytecodes to load
@@ -176,9 +160,151 @@
* <p>
* No calls to methods on this interface should be made after close.
*/
+ @Override
void close();
/**
+ * Search for a provider, then create and return the
+ * {@code ExecutionControl} instance.
+ *
+ * @param env the execution environment (provided by JShell)
+ * @param name the name of provider
+ * @param parameters the parameter map.
+ * @return the execution engine
+ * @throws Throwable an exception that occurred attempting to find or create
+ * the execution engine.
+ * @throws IllegalArgumentException if no ExecutionControlProvider has the
+ * specified {@code name} and {@code parameters}.
+ */
+ static ExecutionControl generate(ExecutionEnv env, String name, Map<String, String> parameters)
+ throws Throwable {
+ Set<String> keys = parameters == null
+ ? Collections.emptySet()
+ : parameters.keySet();
+ for (ExecutionControlProvider p : ServiceLoader.load(ExecutionControlProvider.class)) {
+ if (p.name().equals(name)
+ && p.defaultParameters().keySet().containsAll(keys)) {
+ return p.generate(env, parameters);
+ }
+ }
+ throw new IllegalArgumentException("No ExecutionControlProvider with name '"
+ + name + "' and parameter keys: " + keys.toString());
+ }
+
+ /**
+ * Search for a provider, then create and return the
+ * {@code ExecutionControl} instance.
+ *
+ * @param env the execution environment (provided by JShell)
+ * @param spec the {@code ExecutionControl} spec, which is described in
+ * the documentation of this
+ * {@linkplain jdk.jshell.spi package documentation}.
+ * @return the execution engine
+ * @throws Throwable an exception that occurred attempting to find or create
+ * the execution engine.
+ * @throws IllegalArgumentException if no ExecutionControlProvider has the
+ * specified {@code name} and {@code parameters}.
+ * @throws IllegalArgumentException if {@code spec} is malformed
+ */
+ static ExecutionControl generate(ExecutionEnv env, String spec)
+ throws Throwable {
+ class SpecReader {
+
+ int len = spec.length();
+ int i = -1;
+
+ char ch;
+
+ SpecReader() {
+ next();
+ }
+
+ boolean more() {
+ return i < len;
+ }
+
+ char current() {
+ return ch;
+ }
+
+ final boolean next() {
+ ++i;
+ if (i < len) {
+ ch = spec.charAt(i);
+ return true;
+ }
+ i = len;
+ return false;
+ }
+
+ void skipWhite() {
+ while (more() && Character.isWhitespace(ch)) {
+ next();
+ }
+ }
+
+ String readId() {
+ skipWhite();
+ StringBuilder sb = new StringBuilder();
+ while (more() && Character.isJavaIdentifierPart(ch)) {
+ sb.append(ch);
+ next();
+ }
+ skipWhite();
+ String id = sb.toString();
+ if (id.isEmpty()) {
+ throw new IllegalArgumentException("Expected identifier in " + spec);
+ }
+ return id;
+ }
+
+ void expect(char exp) {
+ skipWhite();
+ if (!more() || ch != exp) {
+ throw new IllegalArgumentException("Expected '" + exp + "' in " + spec);
+ }
+ next();
+ skipWhite();
+ }
+
+ String readValue() {
+ expect('(');
+ int parenDepth = 1;
+ StringBuilder sb = new StringBuilder();
+ while (more()) {
+ if (ch == ')') {
+ --parenDepth;
+ if (parenDepth == 0) {
+ break;
+ }
+ } else if (ch == '(') {
+ ++parenDepth;
+ }
+ sb.append(ch);
+ next();
+ }
+ expect(')');
+ return sb.toString();
+ }
+ }
+ Map<String, String> parameters = new HashMap<>();
+ SpecReader sr = new SpecReader();
+ String name = sr.readId();
+ if (sr.more()) {
+ sr.expect(':');
+ while (sr.more()) {
+ String key = sr.readId();
+ String value = sr.readValue();
+ parameters.put(key, value);
+ if (sr.more()) {
+ sr.expect(',');
+ }
+ }
+ }
+ return generate(env, name, parameters);
+ }
+
+ /**
* Bundles class name with class bytecodes.
*/
public static final class ClassBytecodes implements Serializable {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControlProvider.java Wed Dec 21 20:14:39 2016 -0800
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jshell.spi;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * The provider used by JShell to generate the execution engine needed to
+ * evaluate Snippets. Alternate execution engines can be created by
+ * implementing this interface, then configuring JShell with the provider or
+ * the providers name and parameter specifier.
+ * @author Robert Field
+ */
+public interface ExecutionControlProvider {
+
+ /**
+ * The unique name of this {@code ExecutionControlProvider}. The name must
+ * be a sequence of characters from the Basic Multilingual Plane which are
+ * {@link Character#isJavaIdentifierPart(char) }.
+ *
+ * @return the ExecutionControlProvider's name
+ */
+ String name();
+
+ /**
+ * Create and return the default parameter map for this
+ * {@code ExecutionControlProvider}. The map can optionally be modified;
+ * Modified or unmodified it can be passed to
+ * {@link #generate(jdk.jshell.spi.ExecutionEnv, java.util.Map) }.
+ *
+ * @return the default parameter map
+ */
+ default Map<String,String> defaultParameters() {
+ return Collections.emptyMap();
+ }
+
+ /**
+ * Create and return the {@code ExecutionControl} instance.
+ *
+ * @param env the execution environment, provided by JShell
+ * @param parameters the {@linkplain #defaultParameters() default} or
+ * modified parameter map.
+ * @return the execution engine
+ * @throws Throwable an exception that occurred attempting to create the
+ * execution engine.
+ */
+ ExecutionControl generate(ExecutionEnv env, Map<String,String> parameters)
+ throws Throwable;
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionEnv.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionEnv.java Wed Dec 21 20:14:39 2016 -0800
@@ -28,7 +28,6 @@
import java.io.InputStream;
import java.io.PrintStream;
import java.util.List;
-import jdk.jshell.JShell;
/**
* Functionality made available to a pluggable JShell execution engine. It is
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/package-info.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/package-info.java Wed Dec 21 20:14:39 2016 -0800
@@ -30,9 +30,41 @@
* and in the case of executable Snippets, execution. The JShell
* implementation includes a default execution engine (currently a remote
* process which is JDI controlled). By implementing the
- * {@link jdk.jshell.spi.ExecutionControl} interface and installing it with
- * {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl.Generator) }
- * other execution engines can be used.
+ * {@link jdk.jshell.spi.ExecutionControl} interface and its generating class,
+ * an implementation of the {@link jdk.jshell.spi.ExecutionControlProvider}
+ * interface, and installing it with
+ * {@link jdk.jshell.JShell.Builder#executionEngine(String)}
+ * other execution engines can be used. Where the passed String is an
+ * {@code ExecutionControl} spec.
+ * <p>
+ * The {@code ExecutionControl} spec is the {@code ExecutionControlProvider}
+ * name optionally followed by a parameter specification.
+ * The syntax of the spec is:
+ * <pre>
+ * spec := name : params
+ * | name
+ * name := identifier
+ * params := param , params
+ * | param
+ * |
+ * param := identifier ( value )
+ * </pre>
+ * Where 'name' is the {@code ExecutionControlProvider}
+ * {@linkplain ExecutionControlProvider#name() name}.
+ * Where 'param' is a Map key from
+ * {@link ExecutionControlProvider#defaultParameters()} and the parenthesized
+ * value; See, for example,
+ * {@link jdk.jshell.execution.JdiExecutionControlProvider}.
+ * Where 'identifier' is a sequence of
+ * {@linkplain java.lang.Character#isJavaIdentifierPart(char)
+ * Java identifier part characters} from the Basic Multilingual Plane.
+ * <p>
+ * For example:
+ * <ul>
+ * <li>local</li>
+ * <li>jdi:hostname(localhost)</li>
+ * <li>failover:1(jdi),2(jdi:launch(true),timeout(3000)),3(local)</li>
+ * </ul>
*
* @see jdk.jshell.execution for execution implementation support
*/
--- a/langtools/src/jdk.jshell/share/classes/module-info.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/src/jdk.jshell/share/classes/module-info.java Wed Dec 21 20:14:39 2016 -0800
@@ -56,6 +56,7 @@
requires transitive java.compiler;
requires transitive jdk.jdi;
requires transitive java.prefs;
+ requires java.logging;
requires jdk.compiler;
requires jdk.internal.le;
requires jdk.internal.ed;
@@ -66,7 +67,14 @@
exports jdk.jshell.execution;
exports jdk.jshell.tool;
+ uses jdk.jshell.spi.ExecutionControlProvider;
uses jdk.internal.editor.spi.BuildInEditorProvider;
provides javax.tools.Tool with jdk.internal.jshell.tool.JShellToolProvider;
+ provides jdk.jshell.spi.ExecutionControlProvider
+ with jdk.jshell.execution.JdiExecutionControlProvider;
+ provides jdk.jshell.spi.ExecutionControlProvider
+ with jdk.jshell.execution.LocalExecutionControlProvider;
+ provides jdk.jshell.spi.ExecutionControlProvider
+ with jdk.jshell.execution.FailOverExecutionControlProvider;
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/BadExecutionControlSpecTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8168615
+ * @summary Test bad input to ExecutionControl.generate
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ * jdk.compiler/com.sun.tools.javac.main
+ * jdk.jdeps/com.sun.tools.javap
+ * jdk.jshell/jdk.internal.jshell.tool
+ * @run testng BadExecutionControlSpecTest
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Collections;
+import java.util.List;
+import org.testng.annotations.Test;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionEnv;
+import static org.testng.Assert.fail;
+
+@Test
+public class BadExecutionControlSpecTest {
+ private static void assertIllegal(String spec) throws Throwable {
+ try {
+ ExecutionEnv env = new ExecutionEnv() {
+ @Override
+ public InputStream userIn() {
+ return new ByteArrayInputStream(new byte[0]);
+ }
+
+ @Override
+ public PrintStream userOut() {
+ return new PrintStream(new ByteArrayOutputStream());
+ }
+
+ @Override
+ public PrintStream userErr() {
+ return new PrintStream(new ByteArrayOutputStream());
+ }
+
+ @Override
+ public List<String> extraRemoteVMOptions() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void closeDown() {
+ }
+
+ };
+ ExecutionControl.generate(env, spec);
+ fail("Expected exception -- " + spec);
+ } catch (IllegalArgumentException ex) {
+ // The expected happened
+ }
+ }
+
+ public void syntaxTest() throws Throwable {
+ assertIllegal(":launch(true)");
+ assertIllegal("jdi:launch(true");
+ assertIllegal("jdi:launch(true)$");
+ assertIllegal("jdi:,");
+ }
+
+ public void notFoundTest() throws Throwable {
+ assertIllegal("fruitbats");
+ assertIllegal("jdi:baz(true)");
+ assertIllegal("random:launch(true)");
+ assertIllegal("jdi:,");
+ }
+}
--- a/langtools/test/jdk/jshell/DyingRemoteAgent.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/DyingRemoteAgent.java Wed Dec 21 20:14:39 2016 -0800
@@ -21,10 +21,11 @@
* questions.
*/
+import java.util.Map;
import jdk.jshell.JShell;
-import jdk.jshell.execution.JdiDefaultExecutionControl;
+import jdk.jshell.execution.JdiExecutionControlProvider;
import jdk.jshell.execution.RemoteExecutionControl;
-import static jdk.jshell.execution.JdiDefaultExecutionControl.defaultTimeout;
+import jdk.jshell.spi.ExecutionControlProvider;
class DyingRemoteAgent extends RemoteExecutionControl {
@@ -39,11 +40,13 @@
}
static JShell state(boolean isLaunch, String host) {
- return JShell.builder().executionEngine(
- JdiDefaultExecutionControl.create(
- DyingRemoteAgent.class.getName(),
- isLaunch,
- host,
- defaultTimeout())).build();
+ ExecutionControlProvider ecp = new JdiExecutionControlProvider();
+ Map<String,String> pm = ecp.defaultParameters();
+ pm.put(JdiExecutionControlProvider.PARAM_REMOTE_AGENT, DyingRemoteAgent.class.getName());
+ pm.put(JdiExecutionControlProvider.PARAM_HOST_NAME, host==null? "" : host);
+ pm.put(JdiExecutionControlProvider.PARAM_LAUNCH, ""+isLaunch);
+ return JShell.builder()
+ .executionEngine(ecp, pm)
+ .build();
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ExecutionControlSpecTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8168615
+ * @summary Test ExecutionControlProvider specs can load user ExecutionControlProviders
+ * with direct maps.
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ * jdk.compiler/com.sun.tools.javac.main
+ * jdk.jdeps/com.sun.tools.javap
+ * jdk.jshell/jdk.jshell.execution
+ * jdk.jshell/jdk.jshell.spi
+ * @library /tools/lib
+ * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
+ * @build KullaTesting Compiler
+ * @run testng ExecutionControlSpecTest
+ */
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeMethod;
+
+public class ExecutionControlSpecTest extends KullaTesting {
+
+ ClassLoader ccl;
+
+ @BeforeMethod
+ @Override
+ public void setUp() {
+ String mod = "my.ec";
+ String pkg = "package my.ec;\n";
+ Compiler compiler = new Compiler();
+ Path modDir = Paths.get("mod");
+ compiler.compile(modDir,
+ pkg +
+ "public class PrefixingExecutionControl extends jdk.jshell.execution.LocalExecutionControl {\n" +
+ " @Override\n" +
+ " public String invoke(String className, String methodName)\n" +
+ " throws RunException, InternalException, EngineTerminationException {\n" +
+ " return \"Blah:\" + super.invoke(className, methodName);\n" +
+ " }\n" +
+ "}\n",
+ pkg +
+ "public class PrefixingExecutionControlProvider implements jdk.jshell.spi.ExecutionControlProvider {\n" +
+ " @Override\n" +
+ " public String name() {\n" +
+ " return \"prefixing\";\n" +
+ " }\n" +
+ " @Override\n" +
+ " public jdk.jshell.spi.ExecutionControl generate(jdk.jshell.spi.ExecutionEnv env,\n" +
+ " java.util.Map<String, String> parameters) {\n" +
+ " return new PrefixingExecutionControl();\n" +
+ " }\n" +
+ "}\n",
+ "module my.ec {\n" +
+ " requires transitive jdk.jshell;\n" +
+ " provides jdk.jshell.spi.ExecutionControlProvider\n" +
+ " with my.ec.PrefixingExecutionControlProvider;\n" +
+ " }");
+ Path modPath = compiler.getPath(modDir);
+ ccl = createAndRunFromModule(mod, modPath);
+
+ setUp(builder -> builder.executionEngine("prefixing"));
+ }
+
+ @AfterMethod
+ @Override
+ public void tearDown() {
+ super.tearDown();
+ Thread.currentThread().setContextClassLoader(ccl);
+ }
+
+ @Test
+ public void testPrefix() {
+ assertEval("43;", "Blah:43");
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/FailOverDirectExecutionControlTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8168615
+ * @summary Test that fail-over works for fail-over ExecutionControlProvider
+ * with direct maps.
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ * jdk.compiler/com.sun.tools.javac.main
+ * jdk.jdeps/com.sun.tools.javap
+ * jdk.jshell/jdk.jshell.execution
+ * jdk.jshell/jdk.jshell.spi
+ * @library /tools/lib
+ * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
+ * @build KullaTesting ExecutionControlTestBase Compiler
+ * @run testng FailOverDirectExecutionControlTest
+ */
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeMethod;
+import jdk.jshell.execution.FailOverExecutionControlProvider;
+import jdk.jshell.spi.ExecutionControlProvider;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+@Test
+public class FailOverDirectExecutionControlTest extends ExecutionControlTestBase {
+
+ ClassLoader ccl;
+ ExecutionControlProvider provider;
+ Map<Level, List<String>> logged = new HashMap<>();
+
+ private class LogTestHandler extends Handler {
+
+ LogTestHandler() {
+ setLevel(Level.ALL);
+ setFilter(lr -> lr.getLoggerName().equals("jdk.jshell.execution"));
+ }
+
+ @Override
+ public void publish(LogRecord lr) {
+ List<String> l = logged.get(lr.getLevel());
+ if (l == null) {
+ l = new ArrayList<>();
+ logged.put(lr.getLevel(), l);
+ }
+ l.add(lr.getMessage());
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void close() throws SecurityException {
+ }
+
+ }
+
+ @BeforeMethod
+ @Override
+ public void setUp() {
+ Logger logger = Logger.getLogger("jdk.jshell.execution");
+ logger.setLevel(Level.ALL);
+ logger.addHandler(new LogTestHandler());
+ Compiler compiler = new Compiler();
+ Path modDir = Paths.get("mod");
+ compiler.compile(modDir,
+ "package my.provide; import java.util.Map;\n" +
+ "import jdk.jshell.spi.ExecutionControl;\n" +
+ "import jdk.jshell.spi.ExecutionControlProvider;\n" +
+ "import jdk.jshell.spi.ExecutionEnv;\n" +
+ "public class AlwaysFailingProvider implements ExecutionControlProvider {\n" +
+ " @Override\n" +
+ " public String name() {\n" +
+ " return \"alwaysFailing\";\n" +
+ " }\n" +
+ " @Override\n" +
+ " public ExecutionControl generate(ExecutionEnv env, Map<String, String> parameters) throws Throwable {\n" +
+ " throw new UnsupportedOperationException(\"This operation intentionally broken.\");\n" +
+ " }\n" +
+ "}\n",
+ "module my.provide {\n" +
+ " requires transitive jdk.jshell;\n" +
+ " provides jdk.jshell.spi.ExecutionControlProvider\n" +
+ " with my.provide.AlwaysFailingProvider;\n" +
+ " }");
+ Path modPath = compiler.getPath(modDir);
+ ccl = createAndRunFromModule("my.provide", modPath);
+
+ provider = new FailOverExecutionControlProvider();
+ Map<String, String> pm = provider.defaultParameters();
+ pm.put("0", "alwaysFailing");
+ pm.put("1", "alwaysFailing");
+ pm.put("2", "jdi");
+ setUp(builder -> builder.executionEngine(provider, pm));
+ }
+
+ @AfterMethod
+ @Override
+ public void tearDown() {
+ super.tearDown();
+ Thread.currentThread().setContextClassLoader(ccl);
+ }
+
+ @Override
+ public void variables() {
+ super.variables();
+ assertEquals(logged.get(Level.FINEST).size(), 1);
+ assertEquals(logged.get(Level.FINE).size(), 2);
+ assertEquals(logged.get(Level.WARNING).size(), 2);
+ assertNull(logged.get(Level.SEVERE));
+ String log = logged.get(Level.WARNING).get(0);
+ assertTrue(log.contains("Failure failover -- 0 = alwaysFailing"), log);
+ assertTrue(log.contains("This operation intentionally broken"), log);
+ log = logged.get(Level.WARNING).get(1);
+ assertTrue(log.contains("Failure failover -- 1 = alwaysFailing"), log);
+ assertTrue(log.contains("This operation intentionally broken"), log);
+ log = logged.get(Level.FINEST).get(0);
+ assertTrue(log.contains("Success failover -- 2 = jdi"), log);
+ }
+}
--- a/langtools/test/jdk/jshell/FailOverExecutionControlDyingLaunchTest.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/FailOverExecutionControlDyingLaunchTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 8131029 8160127 8159935 8169519
+ * @bug 8131029 8160127 8159935 8169519 8168615
* @summary Test that fail-over works for fail-over ExecutionControl generators.
* @modules jdk.jshell/jdk.jshell.execution
* jdk.jshell/jdk.jshell.spi
@@ -33,9 +33,6 @@
import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
-import jdk.jshell.execution.JdiDefaultExecutionControl;
-import static jdk.jshell.execution.JdiDefaultExecutionControl.defaultTimeout;
-import static jdk.jshell.execution.Util.failOverExecutionControlGenerator;
@Test
public class FailOverExecutionControlDyingLaunchTest extends ExecutionControlTestBase {
@@ -43,12 +40,7 @@
@BeforeMethod
@Override
public void setUp() {
- setUp(builder -> builder.executionEngine(failOverExecutionControlGenerator(
- JdiDefaultExecutionControl.create(
- DyingRemoteAgent.class.getName(),
- true,
- null,
- defaultTimeout()),
- JdiDefaultExecutionControl.launch())));
+ setUp(builder -> builder.executionEngine(
+ "failover:0(jdi:remoteAgent(DyingRemoteAgent),launch(true)), 4(jdi:launch(true))"));
}
}
--- a/langtools/test/jdk/jshell/FailOverExecutionControlHangingLaunchTest.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/FailOverExecutionControlHangingLaunchTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -33,9 +33,6 @@
import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
-import jdk.jshell.execution.JdiDefaultExecutionControl;
-import static jdk.jshell.execution.JdiDefaultExecutionControl.defaultTimeout;
-import static jdk.jshell.execution.Util.failOverExecutionControlGenerator;
@Test
public class FailOverExecutionControlHangingLaunchTest extends ExecutionControlTestBase {
@@ -43,12 +40,7 @@
@BeforeMethod
@Override
public void setUp() {
- setUp(builder -> builder.executionEngine(failOverExecutionControlGenerator(
- JdiDefaultExecutionControl.create(
- HangingRemoteAgent.class.getName(),
- true,
- null,
- defaultTimeout()),
- JdiDefaultExecutionControl.launch())));
+ setUp(builder -> builder.executionEngine(
+ "failover:0(jdi:remoteAgent(HangingRemoteAgent),launch(true)), 1(jdi:launch(true))"));
}
}
--- a/langtools/test/jdk/jshell/FailOverExecutionControlHangingListenTest.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/FailOverExecutionControlHangingListenTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -34,9 +34,6 @@
import java.net.InetAddress;
import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
-import jdk.jshell.execution.JdiDefaultExecutionControl;
-import static jdk.jshell.execution.JdiDefaultExecutionControl.defaultTimeout;
-import static jdk.jshell.execution.Util.failOverExecutionControlGenerator;
@Test
public class FailOverExecutionControlHangingListenTest extends ExecutionControlTestBase {
@@ -45,12 +42,8 @@
@Override
public void setUp() {
String loopback = InetAddress.getLoopbackAddress().getHostAddress();
- setUp(builder -> builder.executionEngine(failOverExecutionControlGenerator(
- JdiDefaultExecutionControl.create(
- HangingRemoteAgent.class.getName(),
- false,
- loopback,
- defaultTimeout()),
- JdiDefaultExecutionControl.listen(loopback))));
+ setUp(builder -> builder.executionEngine(
+ "failover:0(jdi:remoteAgent(HangingRemoteAgent),hostname(" + loopback + ")),"
+ + "1(jdi:hostname(" + loopback + "))"));
}
}
--- a/langtools/test/jdk/jshell/FailOverExecutionControlTest.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/FailOverExecutionControlTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 8131029 8160127 8159935
+ * @bug 8131029 8160127 8159935 8168615
* @summary Test that fail-over works for fail-over ExecutionControl generators.
* @modules jdk.jshell/jdk.jshell.execution
* jdk.jshell/jdk.jshell.spi
@@ -33,10 +33,6 @@
import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
-import jdk.jshell.execution.JdiDefaultExecutionControl;
-import jdk.jshell.spi.ExecutionControl;
-import jdk.jshell.spi.ExecutionEnv;
-import static jdk.jshell.execution.Util.failOverExecutionControlGenerator;
@Test
public class FailOverExecutionControlTest extends ExecutionControlTestBase {
@@ -44,18 +40,7 @@
@BeforeMethod
@Override
public void setUp() {
- setUp(builder -> builder.executionEngine(failOverExecutionControlGenerator(
- new AlwaysFailingGenerator(),
- new AlwaysFailingGenerator(),
- JdiDefaultExecutionControl.launch())));
+ setUp(builder -> builder.executionEngine("failover:0(nonExistent), 1(nonExistent), 2(jdi:launch(true))"));
}
- class AlwaysFailingGenerator implements ExecutionControl.Generator {
-
- @Override
- public ExecutionControl generate(ExecutionEnv env) throws UnsupportedOperationException {
- throw new UnsupportedOperationException("This operation intentionally broken.");
- }
-
- }
}
--- a/langtools/test/jdk/jshell/HangingRemoteAgent.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/HangingRemoteAgent.java Wed Dec 21 20:14:39 2016 -0800
@@ -21,9 +21,11 @@
* questions.
*/
+import java.util.Map;
import jdk.jshell.JShell;
-import jdk.jshell.execution.JdiDefaultExecutionControl;
+import jdk.jshell.execution.JdiExecutionControlProvider;
import jdk.jshell.execution.RemoteExecutionControl;
+import jdk.jshell.spi.ExecutionControlProvider;
/**
* Hang for three minutes (long enough to cause a timeout).
@@ -51,12 +53,15 @@
}
static JShell state(boolean isLaunch, String host) {
- return JShell.builder().executionEngine(
- JdiDefaultExecutionControl.create(
- HangingRemoteAgent.class.getName(),
- isLaunch,
- host,
- TIMEOUT)).build();
+ ExecutionControlProvider ecp = new JdiExecutionControlProvider();
+ Map<String,String> pm = ecp.defaultParameters();
+ pm.put(JdiExecutionControlProvider.PARAM_REMOTE_AGENT, HangingRemoteAgent.class.getName());
+ pm.put(JdiExecutionControlProvider.PARAM_HOST_NAME, host==null? "" : host);
+ pm.put(JdiExecutionControlProvider.PARAM_LAUNCH, ""+isLaunch);
+ pm.put(JdiExecutionControlProvider.PARAM_TIMEOUT, ""+TIMEOUT);
+ return JShell.builder()
+ .executionEngine(ecp, pm)
+ .build();
}
}
--- a/langtools/test/jdk/jshell/JdiBadOptionLaunchExecutionControlTest.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/JdiBadOptionLaunchExecutionControlTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -31,7 +31,6 @@
import org.testng.annotations.Test;
import jdk.jshell.JShell;
-import jdk.jshell.execution.JdiDefaultExecutionControl;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
@@ -44,7 +43,7 @@
public void badOptionLaunchTest() {
try {
JShell.builder()
- .executionEngine(JdiDefaultExecutionControl.launch())
+ .executionEngine("jdi:launch(true)")
.remoteVMOptions("-BadBadOption")
.build();
} catch (IllegalStateException ex) {
--- a/langtools/test/jdk/jshell/JdiBadOptionListenExecutionControlTest.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/JdiBadOptionListenExecutionControlTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -31,7 +31,6 @@
import org.testng.annotations.Test;
import jdk.jshell.JShell;
-import jdk.jshell.execution.JdiDefaultExecutionControl;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
@@ -44,7 +43,7 @@
public void badOptionListenTest() {
try {
JShell.builder()
- .executionEngine(JdiDefaultExecutionControl.listen(null))
+ .executionEngine("jdi")
.remoteVMOptions("-BadBadOption")
.build();
} catch (IllegalStateException ex) {
--- a/langtools/test/jdk/jshell/JdiBogusHostListenExecutionControlTest.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/JdiBogusHostListenExecutionControlTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 8169519
+ * @bug 8169519 8168615
* @summary Tests for JDI connector failure
* @modules jdk.jshell/jdk.jshell jdk.jshell/jdk.jshell.spi jdk.jshell/jdk.jshell.execution
* @run testng JdiBogusHostListenExecutionControlTest
@@ -31,7 +31,6 @@
import org.testng.annotations.Test;
import jdk.jshell.JShell;
-import jdk.jshell.execution.JdiDefaultExecutionControl;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
@@ -44,7 +43,7 @@
public void badOptionListenTest() {
try {
JShell.builder()
- .executionEngine(JdiDefaultExecutionControl.listen("BattyRumbleBuckets-Snurfle-99-Blip"))
+ .executionEngine("jdi:hostname(BattyRumbleBuckets-Snurfle-99-Blip)")
.build();
} catch (IllegalStateException ex) {
assertTrue(ex.getMessage().startsWith(EXPECTED_ERROR), ex.getMessage());
--- a/langtools/test/jdk/jshell/JdiLaunchingExecutionControlTest.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/JdiLaunchingExecutionControlTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 8164518
+ * @bug 8164518 8168615
* @summary Tests for standard JDI connector (without failover) -- launching
* @modules jdk.jshell/jdk.jshell.execution
* @build KullaTesting ExecutionControlTestBase
@@ -33,7 +33,6 @@
import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
-import jdk.jshell.execution.JdiDefaultExecutionControl;
@Test
public class JdiLaunchingExecutionControlTest extends ExecutionControlTestBase {
@@ -41,6 +40,6 @@
@BeforeMethod
@Override
public void setUp() {
- setUp(builder -> builder.executionEngine(JdiDefaultExecutionControl.launch()));
+ setUp(builder -> builder.executionEngine("jdi:launch(true)"));
}
}
--- a/langtools/test/jdk/jshell/JdiListeningExecutionControlTest.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/JdiListeningExecutionControlTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -33,7 +33,6 @@
import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
-import jdk.jshell.execution.JdiDefaultExecutionControl;
@Test
public class JdiListeningExecutionControlTest extends ExecutionControlTestBase {
@@ -41,6 +40,6 @@
@BeforeMethod
@Override
public void setUp() {
- setUp(builder -> builder.executionEngine(JdiDefaultExecutionControl.listen(null)));
+ setUp(builder -> builder.executionEngine("jdi"));
}
}
--- a/langtools/test/jdk/jshell/JdiListeningLocalhostExecutionControlTest.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/JdiListeningLocalhostExecutionControlTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -33,7 +33,6 @@
import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
-import jdk.jshell.execution.JdiDefaultExecutionControl;
@Test
public class JdiListeningLocalhostExecutionControlTest extends ExecutionControlTestBase {
@@ -41,6 +40,6 @@
@BeforeMethod
@Override
public void setUp() {
- setUp(builder -> builder.executionEngine(JdiDefaultExecutionControl.listen("localhost")));
+ setUp(builder -> builder.executionEngine("jdi:hostname(localhost)"));
}
}
--- a/langtools/test/jdk/jshell/KullaTesting.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/KullaTesting.java Wed Dec 21 20:14:39 2016 -0800
@@ -28,6 +28,10 @@
import java.io.PrintStream;
import java.io.StringWriter;
import java.lang.reflect.Method;
+import java.lang.module.Configuration;
+import java.lang.module.ModuleFinder;
+import java.lang.reflect.Layer;
+import java.nio.file.Paths;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
@@ -209,6 +213,19 @@
classpath = null;
}
+ public ClassLoader createAndRunFromModule(String moduleName, Path modPath) {
+ ModuleFinder finder = ModuleFinder.of(modPath);
+ Layer parent = Layer.boot();
+ Configuration cf = parent.configuration()
+ .resolveRequires(finder, ModuleFinder.of(), Set.of(moduleName));
+ ClassLoader scl = ClassLoader.getSystemClassLoader();
+ Layer layer = parent.defineModulesWithOneLoader(cf, scl);
+ ClassLoader loader = layer.findLoader(moduleName);
+ ClassLoader ccl = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(loader);
+ return ccl;
+ }
+
public List<String> assertUnresolvedDependencies(DeclarationSnippet key, int unresolvedSize) {
List<String> unresolved = getState().unresolvedDependencies(key).collect(toList());
assertEquals(unresolved.size(), unresolvedSize, "Input: " + key.source() + ", checking unresolved: ");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/MyExecutionControl.java Wed Dec 21 20:14:39 2016 -0800
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import com.sun.jdi.VMDisconnectedException;
+import com.sun.jdi.VirtualMachine;
+import jdk.jshell.execution.JdiExecutionControl;
+import jdk.jshell.execution.JdiInitiator;
+import jdk.jshell.execution.Util;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionEnv;
+import static org.testng.Assert.fail;
+import static jdk.jshell.execution.Util.remoteInputOutput;
+
+class MyExecutionControl extends JdiExecutionControl {
+
+ private static final String REMOTE_AGENT = MyRemoteExecutionControl.class.getName();
+ private static final int TIMEOUT = 2000;
+
+ private VirtualMachine vm;
+ private Process process;
+
+ /**
+ * Creates an ExecutionControl instance based on a JDI
+ * {@code ListeningConnector} or {@code LaunchingConnector}.
+ *
+ * Initialize JDI and use it to launch the remote JVM. Set-up a socket for
+ * commands and results. This socket also transports the user
+ * input/output/error.
+ *
+ * @param env the context passed by
+ * {@link jdk.jshell.spi.ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) }
+ * @return the channel
+ * @throws IOException if there are errors in set-up
+ */
+ static ExecutionControl make(ExecutionEnv env, UserJdiUserRemoteTest test) throws IOException {
+ try (final ServerSocket listener = new ServerSocket(0)) {
+ // timeout for socket
+ listener.setSoTimeout(TIMEOUT);
+ int port = listener.getLocalPort();
+
+ // Set-up the JDI connection
+ List<String> opts = new ArrayList<>(env.extraRemoteVMOptions());
+ opts.add("-classpath");
+ opts.add(System.getProperty("java.class.path")
+ + System.getProperty("path.separator")
+ + System.getProperty("user.dir"));
+ JdiInitiator jdii = new JdiInitiator(port,
+ opts, REMOTE_AGENT, true, null, TIMEOUT);
+ VirtualMachine vm = jdii.vm();
+ Process process = jdii.process();
+
+ List<Consumer<String>> deathListeners = new ArrayList<>();
+ deathListeners.add(s -> env.closeDown());
+ Util.detectJdiExitEvent(vm, s -> {
+ for (Consumer<String> h : deathListeners) {
+ h.accept(s);
+ }
+ });
+
+ // Set-up the commands/reslts on the socket. Piggy-back snippet
+ // output.
+ Socket socket = listener.accept();
+ // out before in -- match remote creation so we don't hang
+ OutputStream out = socket.getOutputStream();
+ Map<String, OutputStream> outputs = new HashMap<>();
+ outputs.put("out", env.userOut());
+ outputs.put("err", env.userErr());
+ outputs.put("aux", test.auxStream);
+ Map<String, InputStream> input = new HashMap<>();
+ input.put("in", env.userIn());
+ ExecutionControl myec = remoteInputOutput(socket.getInputStream(),
+ out, outputs, input,
+ (objIn, objOut) -> new MyExecutionControl(objOut, objIn, vm, process, deathListeners));
+ test.currentEC = myec;
+ return myec;
+ }
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param out the output for commands
+ * @param in the input for responses
+ */
+ private MyExecutionControl(ObjectOutput out, ObjectInput in,
+ VirtualMachine vm, Process process,
+ List<Consumer<String>> deathListeners) {
+ super(out, in);
+ this.vm = vm;
+ this.process = process;
+ deathListeners.add(s -> disposeVM());
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ disposeVM();
+ }
+
+ private synchronized void disposeVM() {
+ try {
+ if (vm != null) {
+ vm.dispose(); // This could NPE, so it is caught below
+ vm = null;
+ }
+ } catch (VMDisconnectedException ex) {
+ // Ignore if already closed
+ } catch (Throwable e) {
+ fail("disposeVM threw: " + e);
+ } finally {
+ if (process != null) {
+ process.destroy();
+ process = null;
+ }
+ }
+ }
+
+ @Override
+ protected synchronized VirtualMachine vm() throws EngineTerminationException {
+ if (vm == null) {
+ throw new EngineTerminationException("VM closed");
+ } else {
+ return vm;
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/MyExecutionControlProvider.java Wed Dec 21 20:14:39 2016 -0800
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.util.Map;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionControlProvider;
+import jdk.jshell.spi.ExecutionEnv;
+
+public class MyExecutionControlProvider implements ExecutionControlProvider {
+
+ private final UserJdiUserRemoteTest test;
+
+ MyExecutionControlProvider(UserJdiUserRemoteTest test) {
+ this.test = test;
+ }
+
+ @Override
+ public String name() {
+ return "my";
+ }
+
+ @Override
+ public ExecutionControl generate(ExecutionEnv env, Map<String, String> parameters) throws Throwable {
+ return MyExecutionControl.make(env, test);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/MyRemoteExecutionControl.java Wed Dec 21 20:14:39 2016 -0800
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import jdk.jshell.execution.DirectExecutionControl;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.InternalException;
+import jdk.jshell.spi.ExecutionControl.RunException;
+import static jdk.jshell.execution.Util.forwardExecutionControlAndIO;
+
+/**
+ * A custom remote agent to verify aux channel and custom ExecutionControl.
+ */
+public class MyRemoteExecutionControl extends DirectExecutionControl implements ExecutionControl {
+
+ static PrintStream auxPrint;
+
+ /**
+ * Launch the agent, connecting to the JShell-core over the socket specified
+ * in the command-line argument.
+ *
+ * @param args standard command-line arguments, expectation is the socket
+ * number is the only argument
+ * @throws Exception any unexpected exception
+ */
+ public static void main(String[] args) throws Exception {
+ try {
+ String loopBack = null;
+ Socket socket = new Socket(loopBack, Integer.parseInt(args[0]));
+ InputStream inStream = socket.getInputStream();
+ OutputStream outStream = socket.getOutputStream();
+ Map<String, Consumer<OutputStream>> outputs = new HashMap<>();
+ outputs.put("out", st -> System.setOut(new PrintStream(st, true)));
+ outputs.put("err", st -> System.setErr(new PrintStream(st, true)));
+ outputs.put("aux", st -> { auxPrint = new PrintStream(st, true); });
+ Map<String, Consumer<InputStream>> input = new HashMap<>();
+ input.put("in", st -> System.setIn(st));
+ forwardExecutionControlAndIO(new MyRemoteExecutionControl(), inStream, outStream, outputs, input);
+ } catch (Throwable ex) {
+ throw ex;
+ }
+ }
+
+ @Override
+ public String varValue(String className, String varName)
+ throws RunException, EngineTerminationException, InternalException {
+ auxPrint.print(varName);
+ return super.varValue(className, varName);
+ }
+
+ @Override
+ public Object extensionCommand(String className, Object arg)
+ throws RunException, EngineTerminationException, InternalException {
+ if (!arg.equals("test")) {
+ throw new InternalException("expected extensionCommand arg to be 'test' got: " + arg);
+ }
+ return "ribbit";
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ToolLocalSimpleTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8168615
+ * @summary Test all the ToolSimpleTest tests, but in local execution. Verify --execution flag
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ * jdk.compiler/com.sun.tools.javac.main
+ * jdk.jdeps/com.sun.tools.javap
+ * jdk.jshell/jdk.internal.jshell.tool
+ * @build KullaTesting TestingInputStream ToolSimpleTest
+ * @run testng ToolLocalSimpleTest
+ */
+
+import java.util.Locale;
+import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+
+public class ToolLocalSimpleTest extends ToolSimpleTest {
+
+ @Override
+ public void test(Locale locale, boolean isDefaultStartUp, String[] args, String startUpMessage, ReplTest... tests) {
+ String[] wargs = new String[args.length + 2];
+ wargs[0] = "--execution";
+ wargs[1] = "local";
+ System.arraycopy(args, 0, wargs, 2, args.length);
+ super.test(locale, isDefaultStartUp, wargs, startUpMessage, tests);
+ }
+
+ @Test
+ public void verifyLocal() {
+ System.setProperty("LOCAL_CHECK", "Here");
+ assertEquals(System.getProperty("LOCAL_CHECK"), "Here");
+ test(new String[]{"--no-startup"},
+ a -> assertCommand(a, "System.getProperty(\"LOCAL_CHECK\")", "$1 ==> \"Here\""),
+ a -> assertCommand(a, "System.setProperty(\"LOCAL_CHECK\", \"After\")", "$2 ==> \"Here\"")
+ );
+ assertEquals(System.getProperty("LOCAL_CHECK"), "After");
+ }
+
+ @Override
+ @Test
+ public void testOptionR() {
+ test(new String[]{"-R-Dthe.sound=blorp", "--no-startup"},
+ (a) -> assertCommand(a, "System.getProperty(\"the.sound\")",
+ "$1 ==> null")
+ );
+ }
+
+ @Test
+ public void testOptionBadR() {
+ test(new String[]{"-R-RottenLiver"},
+ (a) -> assertCommand(a, "43",
+ "$1 ==> 43")
+ );
+ }
+
+}
--- a/langtools/test/jdk/jshell/ToolSimpleTest.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/ToolSimpleTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -45,9 +45,9 @@
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
-@Test
public class ToolSimpleTest extends ReplToolTesting {
+ @Test
public void testRemaining() {
test(
(a) -> assertCommand(a, "int z; z =", "z ==> 0"),
@@ -62,6 +62,7 @@
);
}
+ @Test
public void testOpenComment() {
test(
(a) -> assertCommand(a, "int z = /* blah", ""),
@@ -72,6 +73,7 @@
);
}
+ @Test
public void oneLineOfError() {
test(
(a) -> assertCommand(a, "12+", null),
@@ -80,6 +82,7 @@
);
}
+ @Test
public void defineVariables() {
test(
(a) -> assertCommandCheckOutput(a, "/list", assertList()),
@@ -96,6 +99,7 @@
);
}
+ @Test
public void defineMethods() {
test(
(a) -> assertCommandCheckOutput(a, "/list", assertList()),
@@ -112,6 +116,7 @@
);
}
+ @Test
public void defineTypes() {
test(
(a) -> assertCommandCheckOutput(a, "/list", assertList()),
@@ -131,6 +136,7 @@
);
}
+ @Test
public void defineImports() {
test(
(a) -> assertCommandCheckOutput(a, "/list", assertList()),
@@ -150,6 +156,7 @@
);
}
+ @Test
public void defineVar() {
test(
(a) -> assertCommand(a, "int x = 72", "x ==> 72"),
@@ -158,6 +165,7 @@
);
}
+ @Test
public void defineUnresolvedVar() {
test(
(a) -> assertCommand(a, "undefined x",
@@ -166,6 +174,7 @@
);
}
+ @Test
public void testUnresolved() {
test(
(a) -> assertCommand(a, "int f() { return g() + x + new A().a; }",
@@ -178,16 +187,19 @@
);
}
+ @Test
public void testUnknownCommand() {
test((a) -> assertCommand(a, "/unknown",
"| No such command or snippet id: /unknown\n" +
"| Type /help for help."));
}
+ @Test
public void testEmptyClassPath() {
test(after -> assertCommand(after, "/classpath", "| The /classpath command requires a path argument."));
}
+ @Test
public void testNoArgument() {
test(
(a) -> assertCommand(a, "/save",
@@ -199,6 +211,7 @@
);
}
+ @Test
public void testDebug() {
test(
(a) -> assertCommand(a, "/deb", "| Debugging on"),
@@ -208,6 +221,7 @@
);
}
+ @Test
public void testDrop() {
test(false, new String[]{"--no-startup"},
a -> assertVariable(a, "int", "a"),
@@ -240,6 +254,7 @@
);
}
+ @Test
public void testDropNegative() {
test(false, new String[]{"--no-startup"},
a -> assertCommandOutputStartsWith(a, "/drop 0", "| No such snippet: 0"),
@@ -255,6 +270,7 @@
);
}
+ @Test
public void testAmbiguousDrop() {
Consumer<String> check = s -> {
assertTrue(s.startsWith("| The argument references more than one import, variable, method, or class"), s);
@@ -280,6 +296,7 @@
);
}
+ @Test
public void testApplicationOfPost() {
test(
(a) -> assertCommand(a, "/set mode t normal -command", "| Created new feedback mode: t"),
@@ -290,6 +307,7 @@
);
}
+ @Test
public void testHelpLength() {
Consumer<String> testOutput = (s) -> {
List<String> ss = Stream.of(s.split("\n"))
@@ -304,6 +322,7 @@
);
}
+ @Test
public void testHelp() {
test(
(a) -> assertHelp(a, "/?", "/list", "/help", "/exit", "intro"),
@@ -315,6 +334,7 @@
);
}
+ @Test
public void testHelpFormat() {
test(
(a) -> assertCommandCheckOutput(a, "/help", s -> {
@@ -355,6 +375,7 @@
}
}
+ @Test
public void testListArgs() {
String arg = "qqqq";
List<String> startVarList = new ArrayList<>(START_UP);
@@ -377,6 +398,7 @@
);
}
+ @Test
public void testVarsArgs() {
String arg = "qqqq";
List<String> startVarList = new ArrayList<>();
@@ -402,6 +424,7 @@
);
}
+ @Test
public void testMethodsArgs() {
String arg = "qqqq";
List<String> startMethodList = new ArrayList<>(START_UP_CMD_METHOD);
@@ -436,6 +459,7 @@
);
}
+ @Test
public void testTypesArgs() {
String arg = "qqqq";
List<String> startTypeList = new ArrayList<>();
@@ -468,6 +492,8 @@
s -> checkLineToList(s, startTypeList))
);
}
+
+ @Test
public void defineClasses() {
test(
(a) -> assertCommandCheckOutput(a, "/list", assertList()),
@@ -486,6 +512,8 @@
(a) -> assertCommandCheckOutput(a, "/types", assertClasses())
);
}
+
+ @Test
public void testCommandPrefix() {
test(a -> assertCommandCheckOutput(a, "/s",
assertStartsWith("| Command: '/s' is ambiguous: /save, /set")),
@@ -496,6 +524,7 @@
assertStartsWith("| '/save' requires a filename argument.")));
}
+ @Test
public void testOptionQ() {
test(Locale.ROOT, false, new String[]{"-q", "--no-startup"}, "",
(a) -> assertCommand(a, "1+1", "$1 ==> 2"),
@@ -503,12 +532,14 @@
);
}
+ @Test
public void testOptionS() {
test(Locale.ROOT, false, new String[]{"-s", "--no-startup"}, "",
(a) -> assertCommand(a, "1+1", "")
);
}
+ @Test
public void testOptionV() {
test(new String[]{"-v", "--no-startup"},
(a) -> assertCommand(a, "1+1",
@@ -517,6 +548,7 @@
);
}
+ @Test
public void testOptionFeedback() {
test(Locale.ROOT, false, new String[]{"--feedback", "concise", "--no-startup"}, "",
(a) -> assertCommand(a, "1+1", "$1 ==> 2"),
@@ -524,6 +556,7 @@
);
}
+ @Test
public void testCompoundOptions() {
Consumer<String> confirmNoStartup = s -> {
assertEquals(0, Stream.of(s.split("\n"))
@@ -546,6 +579,7 @@
);
}
+ @Test
public void testOptionR() {
test(new String[]{"-R-Dthe.sound=blorp", "--no-startup"},
(a) -> assertCommand(a, "System.getProperty(\"the.sound\")",
@@ -553,6 +587,7 @@
);
}
+ @Test
public void test8156910() {
test(
(a) -> assertCommandOutputContains(a, "System.out.println(\"%5d\", 10);", "%5d"),
--- a/langtools/test/jdk/jshell/UserExecutionControlTest.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/UserExecutionControlTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -23,14 +23,13 @@
/*
* @test
- * @bug 8156101 8159935 8159122
+ * @bug 8156101 8159935 8159122 8168615
* @summary Tests for ExecutionControl SPI
* @build KullaTesting ExecutionControlTestBase
* @run testng UserExecutionControlTest
*/
-import jdk.jshell.execution.LocalExecutionControl;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import org.testng.annotations.BeforeMethod;
@@ -41,12 +40,13 @@
@BeforeMethod
@Override
public void setUp() {
- setUp(builder -> builder.executionEngine(LocalExecutionControl.create()));
+ setUp(builder -> builder.executionEngine("local"));
}
public void verifyLocal() throws ClassNotFoundException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
System.setProperty("LOCAL_CHECK", "TBD");
assertEquals(System.getProperty("LOCAL_CHECK"), "TBD");
+ assertEval("System.getProperty(\"LOCAL_CHECK\")", "\"TBD\"");
assertEval("System.setProperty(\"LOCAL_CHECK\", \"local\")");
assertEquals(System.getProperty("LOCAL_CHECK"), "local");
}
--- a/langtools/test/jdk/jshell/UserJdiUserRemoteTest.java Wed Dec 14 16:32:07 2016 +0100
+++ b/langtools/test/jdk/jshell/UserJdiUserRemoteTest.java Wed Dec 21 20:14:39 2016 -0800
@@ -23,9 +23,9 @@
/*
* @test
- * @bug 8160128 8159935
+ * @bug 8160128 8159935 8168615
* @summary Tests for Aux channel, custom remote agents, custom JDI implementations.
- * @build KullaTesting ExecutionControlTestBase
+ * @build KullaTesting ExecutionControlTestBase MyExecutionControl MyRemoteExecutionControl MyExecutionControlProvider
* @run testng UserJdiUserRemoteTest
*/
import java.io.ByteArrayOutputStream;
@@ -34,34 +34,10 @@
import jdk.jshell.Snippet;
import static jdk.jshell.Snippet.Status.OVERWRITTEN;
import static jdk.jshell.Snippet.Status.VALID;
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import java.net.ServerSocket;
-import java.util.ArrayList;
-import java.util.List;
-import com.sun.jdi.VMDisconnectedException;
-import com.sun.jdi.VirtualMachine;
import jdk.jshell.VarSnippet;
-import jdk.jshell.execution.DirectExecutionControl;
-import jdk.jshell.execution.JdiExecutionControl;
-import jdk.jshell.execution.JdiInitiator;
-import jdk.jshell.execution.Util;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.net.Socket;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Consumer;
import jdk.jshell.spi.ExecutionControl;
import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
-import jdk.jshell.spi.ExecutionEnv;
import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.fail;
-import static jdk.jshell.execution.Util.forwardExecutionControlAndIO;
-import static jdk.jshell.execution.Util.remoteInputOutput;
@Test
public class UserJdiUserRemoteTest extends ExecutionControlTestBase {
@@ -73,7 +49,7 @@
@Override
public void setUp() {
auxStream = new ByteArrayOutputStream();
- setUp(builder -> builder.executionEngine(MyExecutionControl.create(this)));
+ setUp(builder -> builder.executionEngine(new MyExecutionControlProvider(this), null));
}
public void testVarValue() {
@@ -114,174 +90,3 @@
assertActiveKeys();
}
}
-
-class MyExecutionControl extends JdiExecutionControl {
-
- private static final String REMOTE_AGENT = MyRemoteExecutionControl.class.getName();
- private static final int TIMEOUT = 2000;
-
- private VirtualMachine vm;
- private Process process;
-
- /**
- * Creates an ExecutionControl instance based on a JDI
- * {@code LaunchingConnector}.
- *
- * @return the generator
- */
- public static ExecutionControl.Generator create(UserJdiUserRemoteTest test) {
- return env -> make(env, test);
- }
-
- /**
- * Creates an ExecutionControl instance based on a JDI
- * {@code ListeningConnector} or {@code LaunchingConnector}.
- *
- * Initialize JDI and use it to launch the remote JVM. Set-up a socket for
- * commands and results. This socket also transports the user
- * input/output/error.
- *
- * @param env the context passed by
- * {@link jdk.jshell.spi.ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) }
- * @return the channel
- * @throws IOException if there are errors in set-up
- */
- static ExecutionControl make(ExecutionEnv env, UserJdiUserRemoteTest test) throws IOException {
- try (final ServerSocket listener = new ServerSocket(0)) {
- // timeout for socket
- listener.setSoTimeout(TIMEOUT);
- int port = listener.getLocalPort();
-
- // Set-up the JDI connection
- List<String> opts = new ArrayList<>(env.extraRemoteVMOptions());
- opts.add("-classpath");
- opts.add(System.getProperty("java.class.path")
- + System.getProperty("path.separator")
- + System.getProperty("user.dir"));
- JdiInitiator jdii = new JdiInitiator(port,
- opts, REMOTE_AGENT, true, null, TIMEOUT);
- VirtualMachine vm = jdii.vm();
- Process process = jdii.process();
-
- List<Consumer<String>> deathListeners = new ArrayList<>();
- deathListeners.add(s -> env.closeDown());
- Util.detectJdiExitEvent(vm, s -> {
- for (Consumer<String> h : deathListeners) {
- h.accept(s);
- }
- });
-
- // Set-up the commands/reslts on the socket. Piggy-back snippet
- // output.
- Socket socket = listener.accept();
- // out before in -- match remote creation so we don't hang
- OutputStream out = socket.getOutputStream();
- Map<String, OutputStream> outputs = new HashMap<>();
- outputs.put("out", env.userOut());
- outputs.put("err", env.userErr());
- outputs.put("aux", test.auxStream);
- Map<String, InputStream> input = new HashMap<>();
- input.put("in", env.userIn());
- ExecutionControl myec = remoteInputOutput(socket.getInputStream(), out, outputs, input, (objIn, objOut) -> new MyExecutionControl(objOut, objIn, vm, process, deathListeners));
- test.currentEC = myec;
- return myec;
- }
- }
-
- /**
- * Create an instance.
- *
- * @param out the output for commands
- * @param in the input for responses
- */
- private MyExecutionControl(ObjectOutput out, ObjectInput in,
- VirtualMachine vm, Process process,
- List<Consumer<String>> deathListeners) {
- super(out, in);
- this.vm = vm;
- this.process = process;
- deathListeners.add(s -> disposeVM());
- }
-
- @Override
- public void close() {
- super.close();
- disposeVM();
- }
-
- private synchronized void disposeVM() {
- try {
- if (vm != null) {
- vm.dispose(); // This could NPE, so it is caught below
- vm = null;
- }
- } catch (VMDisconnectedException ex) {
- // Ignore if already closed
- } catch (Throwable e) {
- fail("disposeVM threw: " + e);
- } finally {
- if (process != null) {
- process.destroy();
- process = null;
- }
- }
- }
-
- @Override
- protected synchronized VirtualMachine vm() throws EngineTerminationException {
- if (vm == null) {
- throw new EngineTerminationException("VM closed");
- } else {
- return vm;
- }
- }
-
-}
-
-class MyRemoteExecutionControl extends DirectExecutionControl implements ExecutionControl {
-
- static PrintStream auxPrint;
-
- /**
- * Launch the agent, connecting to the JShell-core over the socket specified
- * in the command-line argument.
- *
- * @param args standard command-line arguments, expectation is the socket
- * number is the only argument
- * @throws Exception any unexpected exception
- */
- public static void main(String[] args) throws Exception {
- try {
- String loopBack = null;
- Socket socket = new Socket(loopBack, Integer.parseInt(args[0]));
- InputStream inStream = socket.getInputStream();
- OutputStream outStream = socket.getOutputStream();
- Map<String, Consumer<OutputStream>> outputs = new HashMap<>();
- outputs.put("out", st -> System.setOut(new PrintStream(st, true)));
- outputs.put("err", st -> System.setErr(new PrintStream(st, true)));
- outputs.put("aux", st -> { auxPrint = new PrintStream(st, true); });
- Map<String, Consumer<InputStream>> input = new HashMap<>();
- input.put("in", st -> System.setIn(st));
- forwardExecutionControlAndIO(new MyRemoteExecutionControl(), inStream, outStream, outputs, input);
- } catch (Throwable ex) {
- throw ex;
- }
- }
-
- @Override
- public String varValue(String className, String varName)
- throws RunException, EngineTerminationException, InternalException {
- auxPrint.print(varName);
- return super.varValue(className, varName);
- }
-
- @Override
- public Object extensionCommand(String className, Object arg)
- throws RunException, EngineTerminationException, InternalException {
- if (!arg.equals("test")) {
- throw new InternalException("expected extensionCommand arg to be 'test' got: " + arg);
- }
- return "ribbit";
- }
-
-}