--- old/test/jdk/com/sun/jdi/ExclusiveBind.java 2018-10-22 16:32:11.975459100 -0700 +++ new/test/jdk/com/sun/jdi/ExclusiveBind.java 2018-10-22 16:32:10.334664100 -0700 @@ -27,113 +27,39 @@ * at the same time. * @library /test/lib * - * @modules java.management - * jdk.jdi - * @build VMConnection ExclusiveBind HelloWorld + * @build ExclusiveBind HelloWorld * @run driver ExclusiveBind */ -import java.net.ServerSocket; -import com.sun.jdi.Bootstrap; -import com.sun.jdi.VirtualMachine; -import com.sun.jdi.connect.Connector; -import com.sun.jdi.connect.AttachingConnector; - -import java.util.ArrayList; -import java.util.Map; -import java.util.List; -import java.util.Iterator; -import java.util.concurrent.TimeUnit; import jdk.test.lib.process.ProcessTools; -import jdk.test.lib.Utils; +import lib.jdb.Debuggee; public class ExclusiveBind { /* - * Find a connector by name - */ - private static Connector findConnector(String name) { - List connectors = Bootstrap.virtualMachineManager().allConnectors(); - Iterator iter = connectors.iterator(); - while (iter.hasNext()) { - Connector connector = (Connector)iter.next(); - if (connector.name().equals(name)) { - return connector; - } - } - return null; - } - - /* - * Launch (in server mode) a debuggee with the given address and - * suspend mode. - */ - private static ProcessBuilder prepareLauncher(String address, boolean suspend, String class_name) throws Exception { - List args = new ArrayList<>(); - for(String dbgOption : VMConnection.getDebuggeeVMOptions().split(" ")) { - if (!dbgOption.trim().isEmpty()) { - args.add(dbgOption); - } - } - String lib = "-agentlib:jdwp=transport=dt_socket,server=y,suspend="; - if (suspend) { - lib += "y"; - } else { - lib += "n"; - } - lib += ",address=" + address; - - args.add(lib); - args.add(class_name); - - return ProcessTools.createJavaProcessBuilder(args.toArray(new String[args.size()])); - } - - /* - * - pick a TCP port - * - Launch a debuggee in server=y,suspend=y,address=${port} - * - Launch a second debuggee in server=y,suspend=n with the same port + * - Launch a debuggee with server=y,suspend=y + * - Parse listening port + * - Launch a second debuggee in server=y,suspend=n with the parsed port * - Second debuggee should fail with an error (address already in use) * - For clean-up we attach to the first debuggee and resume it. */ public static void main(String args[]) throws Exception { - // find a free port - ServerSocket ss = new ServerSocket(0); - int port = ss.getLocalPort(); - ss.close(); - - String address = String.valueOf(port); - // launch the first debuggee - ProcessBuilder process1 = prepareLauncher(address, true, "HelloWorld"); - // start the debuggee and wait for the "ready" message - Process p = ProcessTools.startProcess( - "process1", - process1, - line -> line.equals("Listening for transport dt_socket at address: " + address), - Utils.adjustTimeout(5000), - TimeUnit.MILLISECONDS - ); - - // launch a second debuggee with the same address - ProcessBuilder process2 = prepareLauncher(address, false, "HelloWorld"); - - // get exit status from second debuggee - int exitCode = ProcessTools.startProcess("process2", process2).waitFor(); - - // clean-up - attach to first debuggee and resume it - AttachingConnector conn = (AttachingConnector)findConnector("com.sun.jdi.SocketAttach"); - Map conn_args = conn.defaultArguments(); - Connector.IntegerArgument port_arg = - (Connector.IntegerArgument)conn_args.get("port"); - port_arg.setValue(port); - VirtualMachine vm = conn.attach(conn_args); - vm.resume(); - - // if the second debuggee ran to completion then we've got a problem - if (exitCode == 0) { - throw new RuntimeException("Test failed - second debuggee didn't fail to bind"); - } else { - System.out.println("Test passed - second debuggee correctly failed to bind"); + try (Debuggee process1 = Debuggee.launcher("HelloWorld").launch("process1")) { + // launch a second debuggee with the same address + ProcessBuilder process2 = Debuggee.launcher("HelloWorld") + .setSuspended(false) + .setAddress(process1.getAddress()) + .prepare(); + + // get exit status from second debuggee + int exitCode = ProcessTools.startProcess("process2", process2).waitFor(); + + // if the second debuggee ran to completion then we've got a problem + if (exitCode == 0) { + throw new RuntimeException("Test failed - second debuggee didn't fail to bind"); + } else { + System.out.println("Test passed - second debuggee correctly failed to bind"); + } } } } --- old/test/jdk/com/sun/jdi/lib/jdb/JdbTest.java 2018-10-22 16:32:22.527951800 -0700 +++ new/test/jdk/com/sun/jdi/lib/jdb/JdbTest.java 2018-10-22 16:32:20.840279800 -0700 @@ -23,9 +23,7 @@ package lib.jdb; -import jdk.test.lib.Utils; import jdk.test.lib.process.OutputAnalyzer; -import jdk.test.lib.process.ProcessTools; import java.io.IOException; import java.nio.file.Files; @@ -34,9 +32,6 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; public abstract class JdbTest { @@ -76,8 +71,7 @@ } protected Jdb jdb; - protected Process debuggee; - private final List debuggeeOutput = new LinkedList<>(); + protected Debuggee debuggee; private final LaunchOptions launchOptions; // returns the whole jdb output as a string @@ -87,7 +81,7 @@ // returns the whole debuggee output as a string public String getDebuggeeOutput() { - return debuggeeOutput.stream().collect(Collectors.joining(lineSeparator)); + return debuggee == null ? "" : debuggee.getOutput(); } public void run() { @@ -106,45 +100,22 @@ protected void setup() { /* run debuggee as: - java -agentlib:jdwp=transport=dt_socket,address=0,server=n,suspend=y + java -agentlib:jdwp=transport=dt_socket,server=n,suspend=y it reports something like : Listening for transport dt_socket at address: 60810 after that connect jdb by: jdb -connect com.sun.jdi.SocketAttach:port=60810 */ // launch debuggee - List debuggeeArgs = new LinkedList<>(); - // specify address=0 to automatically select free port - debuggeeArgs.add("-agentlib:jdwp=transport=dt_socket,address=0,server=y,suspend=y"); - debuggeeArgs.addAll(launchOptions.debuggeeOptions); - debuggeeArgs.add(launchOptions.debuggeeClass); - ProcessBuilder pbDebuggee = ProcessTools.createJavaProcessBuilder(true, debuggeeArgs.toArray(new String[0])); - - // debuggeeListen[0] - transport, debuggeeListen[1] - address - String[] debuggeeListen = new String[2]; - Pattern listenRegexp = Pattern.compile("Listening for transport \\b(.+)\\b at address: \\b(\\d+)\\b"); - try { - debuggee = ProcessTools.startProcess("debuggee", pbDebuggee, - s -> debuggeeOutput.add(s), // output consumer - s -> { // warm-up predicate - Matcher m = listenRegexp.matcher(s); - if (!m.matches()) { - return false; - } - debuggeeListen[0] = m.group(1); - debuggeeListen[1] = m.group(2); - return true; - }, - 30, TimeUnit.SECONDS); - } catch (IOException | InterruptedException | TimeoutException ex) { - throw new RuntimeException("failed to launch debuggee", ex); - } + debuggee = Debuggee.launcher(launchOptions.debuggeeClass) + .addOptions(launchOptions.debuggeeOptions) + .launch(); // launch jdb try { - jdb = new Jdb("-connect", "com.sun.jdi.SocketAttach:port=" + debuggeeListen[1]); + jdb = new Jdb("-connect", "com.sun.jdi.SocketAttach:port=" + debuggee.getAddress()); } catch (Throwable ex) { // terminate debuggee if something went wrong - debuggee.destroy(); + debuggee.shutdown(); throw ex; } // wait while jdb is initialized @@ -158,15 +129,9 @@ jdb.shutdown(); } // shutdown debuggee - if (debuggee != null && debuggee.isAlive()) { - try { - debuggee.waitFor(Utils.adjustTimeout(10), TimeUnit.SECONDS); - } catch (InterruptedException e) { - // ignore - } finally { - if (debuggee.isAlive()) { - debuggee.destroy(); - } + if (debuggee != null) { + if (!debuggee.waitFor(10, TimeUnit.SECONDS)) { + debuggee.shutdown(); } } } --- /dev/null 2018-10-22 16:32:33.000000000 -0700 +++ new/test/jdk/com/sun/jdi/lib/jdb/Debuggee.java 2018-10-22 16:32:30.450637300 -0700 @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2018, 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. + */ + +package lib.jdb; + +import jdk.test.lib.Utils; +import jdk.test.lib.process.ProcessTools; + +import java.io.Closeable; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Helper class to run java debuggee and parse agent listening transport/address. + * Usage: + * 1) + * Debugee debugee = Debuggee.launcher("MyClass").setTransport("dt_shmem").launch(); + * try { + * String transport = debuggee.getTransport(); + * String addr = debuggee.getAddress(); + * } finally { + * debuggee.shutdown(); + * } + * 2) (using try-with-resource) + * try (Debugee debugee = Debuggee.launcher("MyClass").launch()) { + * String transport = debuggee.getTransport(); + * String addr = debuggee.getAddress(); + * } + * 3) + * ProcessBuilder pb = Debuggee.launcher("MyClass").setSuspended(false).prepare(); + * ProcessTools.executeProcess(pb); + */ +public class Debuggee implements Closeable { + + public static Launcher launcher(String mainClass) { + return new Launcher(mainClass); + } + + public static class Launcher { + private final String mainClass; + private final List options = new LinkedList<>(); + private String transport = "dt_socket"; + private String address = null; + private boolean suspended = true; + private boolean addTestVmAndJavaOptions = true; + + private Launcher(String mainClass) { + this.mainClass = mainClass; + } + public Launcher addOption(String option) { + options.add(option); + return this; + } + public Launcher addOptions(List options) { + this.options.addAll(options); + return this; + } + // default is "dt_socket" + public Launcher setTransport(String value) { + transport = value; + return this; + } + // default is "null" (auto-generate) + public Launcher setAddress(String value) { + address = value; + return this; + } + // default is "true" + public Launcher setSuspended(boolean value) { + suspended = value; + return this; + } + // default is "true" + public Launcher addTestVmAndJavaOptions(boolean value) { + addTestVmAndJavaOptions = value; + return this; + } + + public ProcessBuilder prepare() { + List debuggeeArgs = new LinkedList<>(); + debuggeeArgs.add("-agentlib:jdwp=transport=" + transport + + (address == null ? "" : ",address=" + address) + + ",server=y,suspend=" + (suspended ? "y" : "n")); + debuggeeArgs.addAll(options); + debuggeeArgs.add(mainClass); + return ProcessTools.createJavaProcessBuilder(addTestVmAndJavaOptions, + debuggeeArgs.toArray(new String[0])); + } + + public Debuggee launch(String name) { + return new Debuggee(prepare(), name); + } + public Debuggee launch() { + return launch("debuggee"); + } + } + + // starts the process, waits for "Listening for transport" output and detects transport/address + private Debuggee(ProcessBuilder pb, String name) { + // debuggeeListen[0] - transport, debuggeeListen[1] - address + String[] debuggeeListen = new String[2]; + Pattern listenRegexp = Pattern.compile("Listening for transport \\b(.+)\\b at address: \\b(.+)\\b"); + try { + p = ProcessTools.startProcess(name, pb, + s -> output.add(s), // output consumer + s -> { // warm-up predicate + Matcher m = listenRegexp.matcher(s); + if (!m.matches()) { + return false; + } + debuggeeListen[0] = m.group(1); + debuggeeListen[1] = m.group(2); + return true; + }, + 30, TimeUnit.SECONDS); + transport = debuggeeListen[0]; + address = debuggeeListen[1]; + } catch (IOException | InterruptedException | TimeoutException ex) { + throw new RuntimeException("failed to launch debuggee", ex); + } + } + + private final Process p; + private final List output = new LinkedList<>(); + private final String transport; + private final String address; + + public void shutdown() { + try { + close(); + } catch (IOException ex) { + // ignore + } + } + + // waits until the process shutdown or crash + public boolean waitFor(long timeout, TimeUnit unit) { + try { + return p.waitFor(Utils.adjustTimeout(timeout), unit); + } catch (InterruptedException e) { + return false; + } + } + + // returns the whole debuggee output as a string + public String getOutput() { + return output.stream().collect(Collectors.joining(Utils.NEW_LINE)); + } + + String getTransport() { + return transport; + } + + public String getAddress() { + return address; + } + + @Override + public void close() throws IOException { + if (p.isAlive()) { + p.destroy(); + } + } + +}