1 /* 2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 8160128 8159935 27 * @summary Tests for Aux channel, custom remote agents, custom JDI implementations. 28 * @build KullaTesting ExecutionControlTestBase 29 * @run testng UserJDIUserRemoteTest 30 */ 31 import java.io.ByteArrayOutputStream; 32 import org.testng.annotations.Test; 33 import org.testng.annotations.BeforeMethod; 34 import jdk.jshell.Snippet; 35 import static jdk.jshell.Snippet.Status.OVERWRITTEN; 36 import static jdk.jshell.Snippet.Status.VALID; 37 import java.io.IOException; 38 import java.io.ObjectInput; 39 import java.io.ObjectOutput; 40 import java.net.ServerSocket; 41 import java.util.ArrayList; 42 import java.util.List; 43 import com.sun.jdi.VMDisconnectedException; 44 import com.sun.jdi.VirtualMachine; 45 import jdk.jshell.VarSnippet; 46 import jdk.jshell.execution.DirectExecutionControl; 47 import jdk.jshell.execution.JDIExecutionControl; 48 import jdk.jshell.execution.JDIInitiator; 49 import jdk.jshell.execution.Util; 50 import java.io.InputStream; 51 import java.io.OutputStream; 52 import java.io.PrintStream; 53 import java.net.Socket; 54 55 import java.util.HashMap; 56 import java.util.Map; 57 import java.util.function.Consumer; 58 import jdk.jshell.spi.ExecutionControl; 59 import jdk.jshell.spi.ExecutionControl.ExecutionControlException; 60 import jdk.jshell.spi.ExecutionEnv; 61 import static org.testng.Assert.assertEquals; 62 import static org.testng.Assert.fail; 63 import static jdk.jshell.execution.Util.forwardExecutionControlAndIO; 64 import static jdk.jshell.execution.Util.remoteInputOutput; 65 66 @Test 67 public class UserJDIUserRemoteTest extends ExecutionControlTestBase { 68 69 ExecutionControl currentEC; 70 ByteArrayOutputStream auxStream; 71 72 @BeforeMethod 73 @Override 74 public void setUp() { 75 auxStream = new ByteArrayOutputStream(); 76 setUp(builder -> builder.executionEngine(MyExecutionControl.create(this))); 77 } 78 79 public void testVarValue() { 80 VarSnippet dv = varKey(assertEval("double aDouble = 1.5;")); 81 String vd = getState().varValue(dv); 82 assertEquals(vd, "1.5"); 83 assertEquals(auxStream.toString(), "aDouble"); 84 } 85 86 public void testExtension() throws ExecutionControlException { 87 assertEval("42;"); 88 Object res = currentEC.extensionCommand("FROG", "test"); 89 assertEquals(res, "ribbit"); 90 } 91 92 public void testRedefine() { 93 Snippet vx = varKey(assertEval("int x;")); 94 Snippet mu = methodKey(assertEval("int mu() { return x * 4; }")); 95 Snippet c = classKey(assertEval("class C { String v() { return \"#\" + mu(); } }")); 96 assertEval("C c0 = new C();"); 97 assertEval("c0.v();", "\"#0\""); 98 assertEval("int x = 10;", "10", 99 ste(MAIN_SNIPPET, VALID, VALID, false, null), 100 ste(vx, VALID, OVERWRITTEN, false, MAIN_SNIPPET)); 101 assertEval("c0.v();", "\"#40\""); 102 assertEval("C c = new C();"); 103 assertEval("c.v();", "\"#40\""); 104 assertEval("int mu() { return x * 3; }", 105 ste(MAIN_SNIPPET, VALID, VALID, false, null), 106 ste(mu, VALID, OVERWRITTEN, false, MAIN_SNIPPET)); 107 assertEval("c.v();", "\"#30\""); 108 assertEval("class C { String v() { return \"@\" + mu(); } }", 109 ste(MAIN_SNIPPET, VALID, VALID, false, null), 110 ste(c, VALID, OVERWRITTEN, false, MAIN_SNIPPET)); 111 assertEval("c0.v();", "\"@30\""); 112 assertEval("c = new C();"); 113 assertEval("c.v();", "\"@30\""); 114 assertActiveKeys(); 115 } 116 } 117 118 class MyExecutionControl extends JDIExecutionControl { 119 120 private static final String REMOTE_AGENT = MyRemoteExecutionControl.class.getName(); 121 122 private VirtualMachine vm; 123 private Process process; 124 125 /** 126 * Creates an ExecutionControl instance based on a JDI 127 * {@code LaunchingConnector}. 128 * 129 * @return the generator 130 */ 131 public static ExecutionControl.Generator create(UserJDIUserRemoteTest test) { 132 return env -> make(env, test); 133 } 134 135 /** 136 * Creates an ExecutionControl instance based on a JDI 137 * {@code ListeningConnector} or {@code LaunchingConnector}. 138 * 139 * Initialize JDI and use it to launch the remote JVM. Set-up a socket for 140 * commands and results. This socket also transports the user 141 * input/output/error. 142 * 143 * @param env the context passed by 144 * {@link jdk.jshell.spi.ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) } 145 * @return the channel 146 * @throws IOException if there are errors in set-up 147 */ 148 static ExecutionControl make(ExecutionEnv env, UserJDIUserRemoteTest test) throws IOException { 149 try (final ServerSocket listener = new ServerSocket(0)) { 150 // timeout after 60 seconds 151 listener.setSoTimeout(60000); 152 int port = listener.getLocalPort(); 153 154 // Set-up the JDI connection 155 List<String> opts = new ArrayList<>(env.extraRemoteVMOptions()); 156 opts.add("-classpath"); 157 opts.add(System.getProperty("java.class.path") 158 + System.getProperty("path.separator") 159 + System.getProperty("user.dir")); 160 JDIInitiator jdii = new JDIInitiator(port, 161 opts, REMOTE_AGENT, true); 162 VirtualMachine vm = jdii.vm(); 163 Process process = jdii.process(); 164 165 List<Consumer<String>> deathListeners = new ArrayList<>(); 166 deathListeners.add(s -> env.closeDown()); 167 Util.detectJDIExitEvent(vm, s -> { 168 for (Consumer<String> h : deathListeners) { 169 h.accept(s); 170 } 171 }); 172 173 // Set-up the commands/reslts on the socket. Piggy-back snippet 174 // output. 175 Socket socket = listener.accept(); 176 // out before in -- match remote creation so we don't hang 177 OutputStream out = socket.getOutputStream(); 178 Map<String, OutputStream> outputs = new HashMap<>(); 179 outputs.put("out", env.userOut()); 180 outputs.put("err", env.userErr()); 181 outputs.put("aux", test.auxStream); 182 Map<String, InputStream> input = new HashMap<>(); 183 input.put("in", env.userIn()); 184 ExecutionControl myec = remoteInputOutput(socket.getInputStream(), out, outputs, input, (objIn, objOut) -> new MyExecutionControl(objOut, objIn, vm, process, deathListeners)); 185 test.currentEC = myec; 186 return myec; 187 } 188 } 189 190 /** 191 * Create an instance. 192 * 193 * @param out the output for commands 194 * @param in the input for responses 195 */ 196 private MyExecutionControl(ObjectOutput out, ObjectInput in, 197 VirtualMachine vm, Process process, 198 List<Consumer<String>> deathListeners) { 199 super(out, in); 200 this.vm = vm; 201 this.process = process; 202 deathListeners.add(s -> disposeVM()); 203 } 204 205 @Override 206 public void close() { 207 super.close(); 208 disposeVM(); 209 } 210 211 private synchronized void disposeVM() { 212 try { 213 if (vm != null) { 214 vm.dispose(); // This could NPE, so it is caught below 215 vm = null; 216 } 217 } catch (VMDisconnectedException ex) { 218 // Ignore if already closed 219 } catch (Throwable e) { 220 fail("disposeVM threw: " + e); 221 } finally { 222 if (process != null) { 223 process.destroy(); 224 process = null; 225 } 226 } 227 } 228 229 @Override 230 protected synchronized VirtualMachine vm() throws EngineTerminationException { 231 if (vm == null) { 232 throw new EngineTerminationException("VM closed"); 233 } else { 234 return vm; 235 } 236 } 237 238 } 239 240 class MyRemoteExecutionControl extends DirectExecutionControl implements ExecutionControl { 241 242 static PrintStream auxPrint; 243 244 /** 245 * Launch the agent, connecting to the JShell-core over the socket specified 246 * in the command-line argument. 247 * 248 * @param args standard command-line arguments, expectation is the socket 249 * number is the only argument 250 * @throws Exception any unexpected exception 251 */ 252 public static void main(String[] args) throws Exception { 253 try { 254 String loopBack = null; 255 Socket socket = new Socket(loopBack, Integer.parseInt(args[0])); 256 InputStream inStream = socket.getInputStream(); 257 OutputStream outStream = socket.getOutputStream(); 258 Map<String, Consumer<OutputStream>> outputs = new HashMap<>(); 259 outputs.put("out", st -> System.setOut(new PrintStream(st, true))); 260 outputs.put("err", st -> System.setErr(new PrintStream(st, true))); 261 outputs.put("aux", st -> { auxPrint = new PrintStream(st, true); }); 262 Map<String, Consumer<InputStream>> input = new HashMap<>(); 263 input.put("in", st -> System.setIn(st)); 264 forwardExecutionControlAndIO(new MyRemoteExecutionControl(), inStream, outStream, outputs, input); 265 } catch (Throwable ex) { 266 throw ex; 267 } 268 } 269 270 @Override 271 public String varValue(String className, String varName) 272 throws RunException, EngineTerminationException, InternalException { 273 auxPrint.print(varName); 274 return super.varValue(className, varName); 275 } 276 277 @Override 278 public Object extensionCommand(String className, Object arg) 279 throws RunException, EngineTerminationException, InternalException { 280 if (!arg.equals("test")) { 281 throw new InternalException("expected extensionCommand arg to be 'test' got: " + arg); 282 } 283 return "ribbit"; 284 } 285 286 }