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.io.ObjectOutputStream; 41 import java.net.ServerSocket; 42 import java.util.ArrayList; 43 import java.util.List; 44 import com.sun.jdi.VMDisconnectedException; 45 import com.sun.jdi.VirtualMachine; 46 import jdk.jshell.VarSnippet; 47 import jdk.jshell.execution.DirectExecutionControl; 48 import jdk.jshell.execution.JDIExecutionControl; 49 import jdk.jshell.execution.JDIInitiator; 50 import jdk.jshell.execution.Util; 51 import java.io.InputStream; 52 import java.io.OutputStream; 53 import java.io.PrintStream; 54 import java.net.Socket; 55 56 import java.util.HashMap; 57 import java.util.Map; 58 import java.util.function.Consumer; 59 import jdk.jshell.spi.ExecutionControl; 60 import jdk.jshell.spi.ExecutionControl.ExecutionControlException; 61 import jdk.jshell.spi.ExecutionEnv; 62 import static org.testng.Assert.assertEquals; 63 import static org.testng.Assert.fail; 64 import static jdk.jshell.execution.Util.forwardExecutionControlAndIO; 65 import static jdk.jshell.execution.Util.remoteInput; 66 67 @Test 68 public class UserJDIUserRemoteTest extends ExecutionControlTestBase { 69 70 ExecutionControl currentEC; 71 ByteArrayOutputStream auxStream; 72 73 @BeforeMethod 74 @Override 75 public void setUp() { 76 auxStream = new ByteArrayOutputStream(); 77 setUp(builder -> builder.executionEngine(MyExecutionControl.create(this))); 78 } 79 80 public void testVarValue() { 81 VarSnippet dv = varKey(assertEval("double aDouble = 1.5;")); 82 String vd = getState().varValue(dv); 83 assertEquals(vd, "1.5"); 84 assertEquals(auxStream.toString(), "aDouble"); 85 } 86 87 public void testExtension() throws ExecutionControlException { 88 assertEval("42;"); 89 Object res = currentEC.extensionCommand("FROG", "test"); 90 assertEquals(res, "ribbit"); 91 } 92 93 public void testRedefine() { 94 Snippet vx = varKey(assertEval("int x;")); 95 Snippet mu = methodKey(assertEval("int mu() { return x * 4; }")); 96 Snippet c = classKey(assertEval("class C { String v() { return \"#\" + mu(); } }")); 97 assertEval("C c0 = new C();"); 98 assertEval("c0.v();", "\"#0\""); 99 assertEval("int x = 10;", "10", 100 ste(MAIN_SNIPPET, VALID, VALID, false, null), 101 ste(vx, VALID, OVERWRITTEN, false, MAIN_SNIPPET)); 102 assertEval("c0.v();", "\"#40\""); 103 assertEval("C c = new C();"); 104 assertEval("c.v();", "\"#40\""); 105 assertEval("int mu() { return x * 3; }", 106 ste(MAIN_SNIPPET, VALID, VALID, false, null), 107 ste(mu, VALID, OVERWRITTEN, false, MAIN_SNIPPET)); 108 assertEval("c.v();", "\"#30\""); 109 assertEval("class C { String v() { return \"@\" + mu(); } }", 110 ste(MAIN_SNIPPET, VALID, VALID, false, null), 111 ste(c, VALID, OVERWRITTEN, false, MAIN_SNIPPET)); 112 assertEval("c0.v();", "\"@30\""); 113 assertEval("c = new C();"); 114 assertEval("c.v();", "\"@30\""); 115 assertActiveKeys(); 116 } 117 } 118 119 class MyExecutionControl extends JDIExecutionControl { 120 121 private static final String REMOTE_AGENT = MyRemoteExecutionControl.class.getName(); 122 123 private VirtualMachine vm; 124 private Process process; 125 126 /** 127 * Creates an ExecutionControl instance based on a JDI 128 * {@code LaunchingConnector}. 129 * 130 * @return the generator 131 */ 132 public static ExecutionControl.Generator create(UserJDIUserRemoteTest test) { 133 return env -> make(env, test); 134 } 135 136 /** 137 * Creates an ExecutionControl instance based on a JDI 138 * {@code ListeningConnector} or {@code LaunchingConnector}. 139 * 140 * Initialize JDI and use it to launch the remote JVM. Set-up a socket for 141 * commands and results. This socket also transports the user 142 * input/output/error. 143 * 144 * @param env the context passed by 145 * {@link jdk.jshell.spi.ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) } 146 * @return the channel 147 * @throws IOException if there are errors in set-up 148 */ 149 static MyExecutionControl make(ExecutionEnv env, UserJDIUserRemoteTest test) throws IOException { 150 try (final ServerSocket listener = new ServerSocket(0)) { 151 // timeout after 60 seconds 152 listener.setSoTimeout(60000); 153 int port = listener.getLocalPort(); 154 155 // Set-up the JDI connection 156 List<String> opts = new ArrayList<>(env.extraRemoteVMOptions()); 157 opts.add("-classpath"); 158 opts.add(System.getProperty("java.class.path") 159 + System.getProperty("path.separator") 160 + System.getProperty("user.dir")); 161 JDIInitiator jdii = new JDIInitiator(port, 162 opts, REMOTE_AGENT, true); 163 VirtualMachine vm = jdii.vm(); 164 Process process = jdii.process(); 165 166 List<Consumer<String>> deathListeners = new ArrayList<>(); 167 deathListeners.add(s -> env.closeDown()); 168 Util.detectJDIExitEvent(vm, s -> { 169 for (Consumer<String> h : deathListeners) { 170 h.accept(s); 171 } 172 }); 173 174 // Set-up the commands/reslts on the socket. Piggy-back snippet 175 // output. 176 Socket socket = listener.accept(); 177 // out before in -- match remote creation so we don't hang 178 ObjectOutput cmdout = new ObjectOutputStream(socket.getOutputStream()); 179 Map<String, OutputStream> io = new HashMap<>(); 180 io.put("out", env.userOut()); 181 io.put("err", env.userErr()); 182 io.put("aux", test.auxStream); 183 ObjectInput cmdin = remoteInput(socket.getInputStream(), io); 184 MyExecutionControl myec = new MyExecutionControl(cmdout, cmdin, 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>> chans = new HashMap<>(); 259 chans.put("out", st -> System.setOut(new PrintStream(st, true))); 260 chans.put("err", st -> System.setErr(new PrintStream(st, true))); 261 chans.put("aux", st -> { auxPrint = new PrintStream(st, true); }); 262 forwardExecutionControlAndIO(new MyRemoteExecutionControl(), inStream, outStream, chans); 263 } catch (Throwable ex) { 264 throw ex; 265 } 266 } 267 268 @Override 269 public String varValue(String className, String varName) 270 throws RunException, EngineTerminationException, InternalException { 271 auxPrint.print(varName); 272 return super.varValue(className, varName); 273 } 274 275 @Override 276 public Object extensionCommand(String className, Object arg) 277 throws RunException, EngineTerminationException, InternalException { 278 if (!arg.equals("test")) { 279 throw new InternalException("expected extensionCommand arg to be 'test' got: " + arg); 280 } 281 return "ribbit"; 282 } 283 284 }