1 /*
   2  * Copyright (c) 2018, 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 package lib.jdb;
  25 
  26 import jdk.test.lib.Utils;
  27 import jdk.test.lib.process.ProcessTools;
  28 
  29 import java.io.Closeable;
  30 import java.io.IOException;
  31 import java.util.LinkedList;
  32 import java.util.List;
  33 import java.util.concurrent.TimeUnit;
  34 import java.util.concurrent.TimeoutException;
  35 import java.util.regex.Matcher;
  36 import java.util.regex.Pattern;
  37 import java.util.stream.Collectors;
  38 
  39 /**
  40  * Helper class to run java debuggee and parse agent listening address.
  41  * Usage:
  42  *   try (Debugee debugee = Debuggee.launcher("MyClass").setTransport("dt_shmem").launch()) {
  43  *       String addr = debuggee.getAddress();
  44  *   }
  45  *   or
  46  *   ProcessBuilder pb = Debuggee.launcher("MyClass").setSuspended(false).prepare();
  47  *   ProcessTools.executeProcess(pb);
  48  */
  49 public class Debuggee implements Closeable {
  50 
  51     public static Launcher launcher(String mainClass) {
  52         return new Launcher(mainClass);
  53     }
  54 
  55     public static class Launcher {
  56         private final String mainClass;
  57         private final List<String> options = new LinkedList<>();
  58         private String transport = "dt_socket";
  59         private String address = null;
  60         private boolean suspended = true;
  61         boolean addTestVmAndJavaOptions = true;
  62 
  63         private Launcher(String mainClass) {
  64             this.mainClass = mainClass;
  65         }
  66         public Launcher addOption(String option) {
  67             options.add(option);
  68             return this;
  69         }
  70         public Launcher addOptions(List<String> options) {
  71             this.options.addAll(options);
  72             return this;
  73         }
  74         // default is "dt_socket"
  75         public Launcher setTransport(String value) {
  76             transport = value;
  77             return this;
  78         }
  79         // default is "null" (auto-generate)
  80         public Launcher setAddress(String value) {
  81             address = value;
  82             return this;
  83         }
  84         // default is "true"
  85         public Launcher setSuspended(boolean value) {
  86             suspended = value;
  87             return this;
  88         }
  89         // default is "true"
  90         public Launcher addTestVmAndJavaOptions(boolean value) {
  91             addTestVmAndJavaOptions = value;
  92             return this;
  93         }
  94 
  95         public ProcessBuilder prepare() {
  96             List<String> debuggeeArgs = new LinkedList<>();
  97             debuggeeArgs.add("-agentlib:jdwp=transport=" + transport
  98                     + (address == null ? "" : ",address=" + address)
  99                     + ",server=y,suspend=" + (suspended ? "y" : "n"));
 100             debuggeeArgs.addAll(options);
 101             debuggeeArgs.add(mainClass);
 102             return ProcessTools.createJavaProcessBuilder(addTestVmAndJavaOptions,
 103                     debuggeeArgs.toArray(new String[0]));
 104         }
 105 
 106         public Debuggee launch(String name) {
 107             return new Debuggee(prepare(), name);
 108         }
 109         public Debuggee launch() {
 110             return launch("debuggee");
 111         }
 112     }
 113 
 114     // starts the process, waits for "Listening for transport" output and detects transport/address
 115     private Debuggee(ProcessBuilder pb, String name) {
 116         // debuggeeListen[0] - transport, debuggeeListen[1] - address
 117         String[] debuggeeListen = new String[2];
 118         Pattern listenRegexp = Pattern.compile("Listening for transport \\b(.+)\\b at address: \\b(.+)\\b");
 119         try {
 120             p = ProcessTools.startProcess(name, pb,
 121                     s -> output.add(s),  // output consumer
 122                     s -> {  // warm-up predicate
 123                         Matcher m = listenRegexp.matcher(s);
 124                         if (!m.matches()) {
 125                             return false;
 126                         }
 127                         debuggeeListen[0] = m.group(1);
 128                         debuggeeListen[1] = m.group(2);
 129                         return true;
 130                     },
 131                     30, TimeUnit.SECONDS);
 132             transport = debuggeeListen[0];
 133             address = debuggeeListen[1];
 134         } catch (IOException | InterruptedException | TimeoutException ex) {
 135             throw new RuntimeException("failed to launch debuggee", ex);
 136         }
 137     }
 138 
 139     private final Process p;
 140     private final List<String> output = new LinkedList<>();
 141     private final String transport;
 142     private final String address;
 143 
 144     public void shutdown() {
 145         try {
 146             close();
 147         } catch (IOException ex) {
 148             // ignore
 149         }
 150     }
 151 
 152     // waits until the process shutdown or crash
 153     public boolean waitFor(long timeout, TimeUnit unit) {
 154         try {
 155             return p.waitFor(Utils.adjustTimeout(timeout), unit);
 156         } catch (InterruptedException e) {
 157             return false;
 158         }
 159     }
 160 
 161     Process getProcess() {
 162         return p;
 163     }
 164 
 165     // returns the whole debuggee output as a string
 166     public String getOutput() {
 167         return output.stream().collect(Collectors.joining(Utils.NEW_LINE));
 168     }
 169 
 170     String getTransport() {
 171         return transport;
 172     }
 173 
 174     public String getAddress() {
 175         return address;
 176     }
 177 
 178     // helper for dt_socket transport
 179     public int getPort() {
 180         return Integer.parseInt(address);
 181     }
 182 
 183     @Override
 184     public void close() throws IOException {
 185         if (p.isAlive()) {
 186             p.destroy();
 187         }
 188     }
 189 
 190 }