1 /*
   2  * Copyright (c) 2013, 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 jdk.testlibrary;
  25 
  26 import java.io.ByteArrayOutputStream;
  27 import java.io.IOException;
  28 import java.io.OutputStream;
  29 import java.io.OutputStreamWriter;
  30 import java.io.PrintStream;
  31 import java.io.PrintWriter;
  32 import java.lang.management.ManagementFactory;
  33 import java.lang.management.RuntimeMXBean;
  34 import java.lang.reflect.Field;
  35 import java.lang.reflect.Method;
  36 import java.util.ArrayList;
  37 import java.util.Collections;
  38 import java.util.concurrent.ExecutionException;
  39 import java.util.concurrent.Future;
  40 import java.util.concurrent.Phaser;
  41 import java.util.concurrent.TimeUnit;
  42 import java.util.concurrent.TimeoutException;
  43 import java.util.function.Predicate;
  44 
  45 import sun.management.VMManagement;
  46 
  47 public final class ProcessTools {
  48     private static final class LineForwarder extends StreamPumper.LinePump {
  49         private final PrintStream ps;
  50         private final String prefix;
  51         LineForwarder(String prefix, PrintStream os) {
  52             this.ps = os;
  53             this.prefix = prefix;
  54         }
  55         @Override
  56         protected void processLine(String line) {
  57             ps.println("[" + prefix + "] " + line);
  58         }
  59     }
  60 
  61     private ProcessTools() {
  62     }
  63 
  64     /**
  65      * <p>Starts a process from its builder.</p>
  66      * <span>The default redirects of STDOUT and STDERR are started</span>
  67      * @param name The process name
  68      * @param processBuilder The process builder
  69      * @return Returns the initialized process
  70      * @throws IOException
  71      */
  72     public static Process startProcess(String name,
  73                                        ProcessBuilder processBuilder)
  74     throws IOException {
  75         Process p = null;
  76         try {
  77             p = startProcess(name, processBuilder, null, -1, TimeUnit.NANOSECONDS);
  78         } catch (InterruptedException | TimeoutException e) {
  79             // can't ever happen
  80         }
  81         return p;
  82     }
  83 
  84     /**
  85      * <p>Starts a process from its builder.</p>
  86      * <span>The default redirects of STDOUT and STDERR are started</span>
  87      * <p>
  88      * It is possible to wait for the process to get to a warmed-up state
  89      * via {@linkplain Predicate} condition on the STDOUT
  90      * </p>
  91      * @param name The process name
  92      * @param processBuilder The process builder
  93      * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
  94      *                      Used to determine the moment the target app is
  95      *                      properly warmed-up.
  96      *                      It can be null - in that case the warmup is skipped.
  97      * @param timeout The timeout for the warmup waiting
  98      * @param unit The timeout {@linkplain TimeUnit}
  99      * @return Returns the initialized {@linkplain Process}
 100      * @throws IOException
 101      * @throws InterruptedException
 102      * @throws TimeoutException
 103      */
 104     public static Process startProcess(String name,
 105                                        ProcessBuilder processBuilder,
 106                                        final Predicate<String> linePredicate,
 107                                        long timeout,
 108                                        TimeUnit unit)
 109     throws IOException, InterruptedException, TimeoutException {
 110         Process p = processBuilder.start();
 111         StreamPumper stdout = new StreamPumper(p.getInputStream());
 112         StreamPumper stderr = new StreamPumper(p.getErrorStream());
 113 
 114         stdout.addPump(new LineForwarder(name, System.out));
 115         stderr.addPump(new LineForwarder(name, System.err));
 116         final Phaser phs = new Phaser(1);
 117         if (linePredicate != null) {
 118             stdout.addPump(new StreamPumper.LinePump() {
 119                 @Override
 120                 protected void processLine(String line) {
 121                     if (linePredicate.test(line)) {
 122                         if (phs.getRegisteredParties() > 0) {
 123                             phs.arriveAndDeregister();
 124                         }
 125                     }
 126                 }
 127             });
 128         }
 129         Future<Void> stdoutTask = stdout.process();
 130         Future<Void> stderrTask = stderr.process();
 131 
 132         try {
 133             if (timeout > -1) {
 134                 phs.awaitAdvanceInterruptibly(0, timeout, unit);
 135             }
 136         } catch (TimeoutException | InterruptedException e) {
 137             stdoutTask.cancel(true);
 138             stderrTask.cancel(true);
 139             throw e;
 140         }
 141 
 142         return p;
 143     }
 144 
 145     /**
 146      * Pumps stdout and stderr from running the process into a String.
 147      *
 148      * @param processBuilder
 149      *            ProcessHandler to run.
 150      * @return Output from process.
 151      * @throws IOException
 152      *             If an I/O error occurs.
 153      */
 154     public static OutputBuffer getOutput(ProcessBuilder processBuilder)
 155             throws IOException {
 156         return getOutput(processBuilder.start());
 157     }
 158 
 159     /**
 160      * Pumps stdout and stderr the running process into a String.
 161      *
 162      * @param process
 163      *            Process to pump.
 164      * @return Output from process.
 165      * @throws IOException
 166      *             If an I/O error occurs.
 167      */
 168     public static OutputBuffer getOutput(Process process) throws IOException {
 169         ByteArrayOutputStream stderrBuffer = new ByteArrayOutputStream();
 170         ByteArrayOutputStream stdoutBuffer = new ByteArrayOutputStream();
 171         StreamPumper outPumper = new StreamPumper(process.getInputStream(),
 172                 stdoutBuffer);
 173         StreamPumper errPumper = new StreamPumper(process.getErrorStream(),
 174                 stderrBuffer);
 175 
 176         Future<Void> outTask = outPumper.process();
 177         Future<Void> errTask = errPumper.process();
 178 
 179         try {
 180             process.waitFor();
 181             outTask.get();
 182             errTask.get();
 183         } catch (InterruptedException e) {
 184             Thread.currentThread().interrupt();
 185             return null;
 186         } catch (ExecutionException e) {
 187             throw new IOException(e);
 188         }
 189 
 190         return new OutputBuffer(stdoutBuffer.toString(),
 191                 stderrBuffer.toString());
 192     }
 193 
 194     /**
 195      * Get the process id of the current running Java process
 196      *
 197      * @return Process id
 198      */
 199     public static int getProcessId() throws Exception {
 200 
 201         // Get the current process id using a reflection hack
 202         RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
 203         Field jvm = runtime.getClass().getDeclaredField("jvm");
 204 
 205         jvm.setAccessible(true);
 206         VMManagement mgmt = (sun.management.VMManagement) jvm.get(runtime);
 207 
 208         Method pid_method = mgmt.getClass().getDeclaredMethod("getProcessId");
 209 
 210         pid_method.setAccessible(true);
 211 
 212         int pid = (Integer) pid_method.invoke(mgmt);
 213 
 214         return pid;
 215     }
 216 
 217     /**
 218      * Get platform specific VM arguments (e.g. -d64 on 64bit Solaris)
 219      *
 220      * @return String[] with platform specific arguments, empty if there are
 221      *         none
 222      */
 223     public static String[] getPlatformSpecificVMArgs() {
 224         String osName = System.getProperty("os.name");
 225         String dataModel = System.getProperty("sun.arch.data.model");
 226 
 227         if (osName.equals("SunOS") && dataModel.equals("64")) {
 228             return new String[] { "-d64" };
 229         }
 230 
 231         return new String[] {};
 232     }
 233 
 234     /**
 235      * Create ProcessBuilder using the java launcher from the jdk to be tested
 236      * and with any platform specific arguments prepended
 237      */
 238     public static ProcessBuilder createJavaProcessBuilder(String... command)
 239             throws Exception {
 240         String javapath = JdkFinder.getJavaLauncher(false);
 241 
 242         ArrayList<String> args = new ArrayList<>();
 243         args.add(javapath);
 244         Collections.addAll(args, getPlatformSpecificVMArgs());
 245         Collections.addAll(args, command);
 246 
 247         return new ProcessBuilder(args.toArray(new String[args.size()]));
 248 
 249     }
 250 
 251 }