1 /* 2 * Copyright (c) 2015, 2015, 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 package org.graalvm.compiler.test; 26 27 import java.io.BufferedReader; 28 import java.io.File; 29 import java.io.IOException; 30 import java.io.InputStreamReader; 31 import java.nio.file.Files; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.Formatter; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.regex.Matcher; 38 import java.util.regex.Pattern; 39 40 import org.graalvm.compiler.serviceprovider.GraalServices; 41 import org.graalvm.util.CollectionsUtil; 42 43 /** 44 * Utility methods for spawning a VM in a subprocess during unit tests. 45 */ 46 public final class SubprocessUtil { 47 48 private SubprocessUtil() { 49 } 50 51 /** 52 * Gets the command line for the current process. 53 * 54 * @return the command line arguments for the current process or {@code null} if they are not 55 * available 56 */ 57 public static List<String> getProcessCommandLine() { 58 String processArgsFile = System.getenv().get("MX_SUBPROCESS_COMMAND_FILE"); 59 if (processArgsFile != null) { 60 try { 61 return Files.readAllLines(new File(processArgsFile).toPath()); 62 } catch (IOException e) { 63 } 64 } 65 return null; 66 } 67 68 /** 69 * Pattern for a single shell command argument that does not need to quoted. 70 */ 71 private static final Pattern SAFE_SHELL_ARG = Pattern.compile("[A-Za-z0-9@%_\\-\\+=:,\\./]+"); 72 73 /** 74 * Reliably quote a string as a single shell command argument. 75 */ 76 public static String quoteShellArg(String arg) { 77 if (arg.isEmpty()) { 78 return "\"\""; 79 } 80 Matcher m = SAFE_SHELL_ARG.matcher(arg); 81 if (m.matches()) { 82 return arg; 83 } 84 // See http://stackoverflow.com/a/1250279 85 return "'" + arg.replace("'", "'\"'\"'") + "'"; 86 } 87 88 /** 89 * Returns a new copy {@code args} with debugger arguments removed. 90 */ 91 public static List<String> withoutDebuggerArguments(List<String> args) { 92 List<String> result = new ArrayList<>(args.size()); 93 for (String arg : args) { 94 if (!(arg.equals("-Xdebug") || arg.startsWith("-Xrunjdwp:"))) { 95 result.add(arg); 96 } 97 } 98 return result; 99 } 100 101 /** 102 * Gets the command line used to start the current Java VM, including all VM arguments, but not 103 * including the main class or any Java arguments. This can be used to spawn an identical VM, 104 * but running different Java code. 105 */ 106 public static List<String> getVMCommandLine() { 107 List<String> args = getProcessCommandLine(); 108 if (args == null) { 109 return null; 110 } else { 111 int index = findMainClassIndex(args); 112 return args.subList(0, index); 113 } 114 } 115 116 /** 117 * Detects whether a java agent is attached. 118 */ 119 public static boolean isJavaAgentAttached() { 120 return SubprocessUtil.getVMCommandLine().stream().anyMatch(args -> args.startsWith("-javaagent")); 121 } 122 123 /** 124 * The details of a subprocess execution. 125 */ 126 public static class Subprocess { 127 128 /** 129 * The command line of the subprocess. 130 */ 131 public final List<String> command; 132 133 /** 134 * Exit code of the subprocess. 135 */ 136 public final int exitCode; 137 138 /** 139 * Output from the subprocess broken into lines. 140 */ 141 public final List<String> output; 142 143 public Subprocess(List<String> command, int exitCode, List<String> output) { 144 this.command = command; 145 this.exitCode = exitCode; 146 this.output = output; 147 } 148 149 public static final String DASHES_DELIMITER = "-------------------------------------------------------"; 150 151 /** 152 * Returns the command followed by the output as a string. 153 * 154 * @param delimiter if non-null, the returned string has this value as a prefix and suffix 155 */ 156 public String toString(String delimiter) { 157 Formatter msg = new Formatter(); 158 if (delimiter != null) { 159 msg.format("%s%n", delimiter); 160 } 161 msg.format("%s%n", CollectionsUtil.mapAndJoin(command, e -> quoteShellArg(String.valueOf(e)), " ")); 162 for (String line : output) { 163 msg.format("%s%n", line); 164 } 165 if (delimiter != null) { 166 msg.format("%s%n", delimiter); 167 } 168 return msg.toString(); 169 } 170 171 /** 172 * Returns the command followed by the output as a string delimited by 173 * {@value #DASHES_DELIMITER}. 174 */ 175 @Override 176 public String toString() { 177 return toString(DASHES_DELIMITER); 178 } 179 } 180 181 /** 182 * Executes a Java subprocess. 183 * 184 * @param vmArgs the VM arguments 185 * @param mainClassAndArgs the main class and its arguments 186 */ 187 public static Subprocess java(List<String> vmArgs, String... mainClassAndArgs) throws IOException, InterruptedException { 188 return java(vmArgs, Arrays.asList(mainClassAndArgs)); 189 } 190 191 /** 192 * Executes a Java subprocess. 193 * 194 * @param vmArgs the VM arguments 195 * @param mainClassAndArgs the main class and its arguments 196 */ 197 public static Subprocess java(List<String> vmArgs, List<String> mainClassAndArgs) throws IOException, InterruptedException { 198 return javaHelper(vmArgs, null, mainClassAndArgs); 199 } 200 201 /** 202 * Executes a Java subprocess. 203 * 204 * @param vmArgs the VM arguments 205 * @param env the environment variables 206 * @param mainClassAndArgs the main class and its arguments 207 */ 208 public static Subprocess java(List<String> vmArgs, Map<String, String> env, String... mainClassAndArgs) throws IOException, InterruptedException { 209 return java(vmArgs, env, Arrays.asList(mainClassAndArgs)); 210 } 211 212 /** 213 * Executes a Java subprocess. 214 * 215 * @param vmArgs the VM arguments 216 * @param env the environment variables 217 * @param mainClassAndArgs the main class and its arguments 218 */ 219 public static Subprocess java(List<String> vmArgs, Map<String, String> env, List<String> mainClassAndArgs) throws IOException, InterruptedException { 220 return javaHelper(vmArgs, env, mainClassAndArgs); 221 } 222 223 /** 224 * Executes a Java subprocess. 225 * 226 * @param vmArgs the VM arguments 227 * @param env the environment variables 228 * @param mainClassAndArgs the main class and its arguments 229 */ 230 private static Subprocess javaHelper(List<String> vmArgs, Map<String, String> env, List<String> mainClassAndArgs) throws IOException, InterruptedException { 231 List<String> command = new ArrayList<>(vmArgs); 232 command.addAll(mainClassAndArgs); 233 ProcessBuilder processBuilder = new ProcessBuilder(command); 234 if (env != null) { 235 Map<String, String> processBuilderEnv = processBuilder.environment(); 236 processBuilderEnv.putAll(env); 237 } 238 processBuilder.redirectErrorStream(true); 239 Process process = processBuilder.start(); 240 BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream())); 241 String line; 242 List<String> output = new ArrayList<>(); 243 while ((line = stdout.readLine()) != null) { 244 output.add(line); 245 } 246 return new Subprocess(command, process.waitFor(), output); 247 } 248 249 private static final boolean isJava8OrEarlier = GraalServices.Java8OrEarlier; 250 251 private static boolean hasArg(String optionName) { 252 if (optionName.equals("-cp") || optionName.equals("-classpath")) { 253 return true; 254 } 255 if (!isJava8OrEarlier) { 256 if (optionName.equals("--version") || 257 optionName.equals("--show-version") || 258 optionName.equals("--dry-run") || 259 optionName.equals("--disable-@files") || 260 optionName.equals("--dry-run") || 261 optionName.equals("--help") || 262 optionName.equals("--help-extra")) { 263 return false; 264 } 265 if (optionName.startsWith("--")) { 266 return optionName.indexOf('=') == -1; 267 } 268 } 269 return false; 270 } 271 272 private static int findMainClassIndex(List<String> commandLine) { 273 int i = 1; // Skip the java executable 274 275 while (i < commandLine.size()) { 276 String s = commandLine.get(i); 277 if (s.charAt(0) != '-') { 278 return i; 279 } else if (hasArg(s)) { 280 i += 2; 281 } else { 282 i++; 283 } 284 } 285 throw new InternalError(); 286 } 287 288 }