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 * The details of a subprocess execution. 118 */ 119 public static class Subprocess { 120 121 /** 122 * The command line of the subprocess. 123 */ 124 public final List<String> command; 125 126 /** 127 * Exit code of the subprocess. 128 */ 129 public final int exitCode; 130 131 /** 132 * Output from the subprocess broken into lines. 133 */ 134 public final List<String> output; 135 136 public Subprocess(List<String> command, int exitCode, List<String> output) { 137 this.command = command; 138 this.exitCode = exitCode; 139 this.output = output; 140 } 141 142 public static final String DASHES_DELIMITER = "-------------------------------------------------------"; 143 144 /** 145 * Returns the command followed by the output as a string. 146 * 147 * @param delimiter if non-null, the returned string has this value as a prefix and suffix 148 */ 149 public String toString(String delimiter) { 150 Formatter msg = new Formatter(); 151 if (delimiter != null) { 152 msg.format("%s%n", delimiter); 153 } 154 msg.format("%s%n", CollectionsUtil.mapAndJoin(command, e -> quoteShellArg(String.valueOf(e)), " ")); 155 for (String line : output) { 156 msg.format("%s%n", line); 157 } 158 if (delimiter != null) { 159 msg.format("%s%n", delimiter); 160 } 161 return msg.toString(); 162 } 163 164 /** 165 * Returns the command followed by the output as a string delimited by 166 * {@value #DASHES_DELIMITER}. 167 */ 168 @Override 169 public String toString() { 170 return toString(DASHES_DELIMITER); 171 } 172 } 173 174 /** 175 * Executes a Java subprocess. 176 * 177 * @param vmArgs the VM arguments 178 * @param mainClassAndArgs the main class and its arguments 179 */ 180 public static Subprocess java(List<String> vmArgs, String... mainClassAndArgs) throws IOException, InterruptedException { 181 return java(vmArgs, Arrays.asList(mainClassAndArgs)); 182 } 183 184 /** 185 * Executes a Java subprocess. 186 * 187 * @param vmArgs the VM arguments 188 * @param mainClassAndArgs the main class and its arguments 189 */ 190 public static Subprocess java(List<String> vmArgs, List<String> mainClassAndArgs) throws IOException, InterruptedException { 191 return javaHelper(vmArgs, null, mainClassAndArgs); 192 } 193 194 /** 195 * Executes a Java subprocess. 196 * 197 * @param vmArgs the VM arguments 198 * @param env the environment variables 199 * @param mainClassAndArgs the main class and its arguments 200 */ 201 public static Subprocess java(List<String> vmArgs, Map<String, String> env, String... mainClassAndArgs) throws IOException, InterruptedException { 202 return java(vmArgs, env, Arrays.asList(mainClassAndArgs)); 203 } 204 205 /** 206 * Executes a Java subprocess. 207 * 208 * @param vmArgs the VM arguments 209 * @param env the environment variables 210 * @param mainClassAndArgs the main class and its arguments 211 */ 212 public static Subprocess java(List<String> vmArgs, Map<String, String> env, List<String> mainClassAndArgs) throws IOException, InterruptedException { 213 return javaHelper(vmArgs, env, mainClassAndArgs); 214 } 215 216 /** 217 * Executes a Java subprocess. 218 * 219 * @param vmArgs the VM arguments 220 * @param env the environment variables 221 * @param mainClassAndArgs the main class and its arguments 222 */ 223 private static Subprocess javaHelper(List<String> vmArgs, Map<String, String> env, List<String> mainClassAndArgs) throws IOException, InterruptedException { 224 List<String> command = new ArrayList<>(vmArgs); 225 command.addAll(mainClassAndArgs); 226 ProcessBuilder processBuilder = new ProcessBuilder(command); 227 if (env != null) { 228 Map<String, String> processBuilderEnv = processBuilder.environment(); 229 processBuilderEnv.putAll(env); 230 } 231 processBuilder.redirectErrorStream(true); 232 Process process = processBuilder.start(); 233 BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream())); 234 String line; 235 List<String> output = new ArrayList<>(); 236 while ((line = stdout.readLine()) != null) { 237 output.add(line); 238 } 239 return new Subprocess(command, process.waitFor(), output); 240 } 241 242 private static final boolean isJava8OrEarlier = GraalServices.Java8OrEarlier; 243 244 private static boolean hasArg(String optionName) { 245 if (optionName.equals("-cp") || optionName.equals("-classpath")) { 246 return true; 247 } 248 if (!isJava8OrEarlier) { 249 if (optionName.equals("--version") || 250 optionName.equals("--show-version") || 251 optionName.equals("--dry-run") || 252 optionName.equals("--disable-@files") || 253 optionName.equals("--dry-run") || 254 optionName.equals("--help") || 255 optionName.equals("--help-extra")) { 256 return false; 257 } 258 if (optionName.startsWith("--")) { 259 return optionName.indexOf('=') == -1; 260 } 261 } 262 return false; 263 } 264 265 private static int findMainClassIndex(List<String> commandLine) { 266 int i = 1; // Skip the java executable 267 268 while (i < commandLine.size()) { 269 String s = commandLine.get(i); 270 if (s.charAt(0) != '-') { 271 return i; 272 } else if (hasArg(s)) { 273 i += 2; 274 } else { 275 i++; 276 } 277 } 278 throw new InternalError(); 279 } 280 281 }