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 import java.io.BufferedReader; 25 import java.io.File; 26 import java.io.IOException; 27 import java.io.InputStreamReader; 28 import java.net.URL; 29 import java.net.URLClassLoader; 30 import java.nio.file.Files; 31 import java.nio.file.Path; 32 import java.nio.file.Paths; 33 import java.security.Permission; 34 import java.util.ArrayList; 35 import java.util.Base64; 36 import java.util.Collections; 37 import java.util.HashMap; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.Map.Entry; 41 42 /** 43 * This is a test library that makes writing a Java test that spawns multiple 44 * Java processes easily. 45 * 46 * Usage: 47 * 48 * Proc.create("Clazz") // The class to launch 49 * .args("x") // with args 50 * .env("env", "value") // and an environment variable 51 * .prop("key","value") // and a system property 52 * .perm(perm) // with granted permissions 53 * .start(); // and start 54 * 55 * create/start must be called, args/env/prop/perm can be called zero or 56 * multiple times between create and start. 57 * 58 * The controller can call inheritIO to share its I/O to the process. 59 * Otherwise, it can send data into a proc's stdin with write/println, and 60 * read its stdout with readLine. stderr is always redirected to DFILE 61 * unless nodump() is called. A protocol is designed to make 62 * data exchange among the controller and the processes super easy, in which 63 * useful data are always printed with a special prefix ("PROCISFUN:"). 64 * If the data is binary, make it BASE64. 65 * 66 * For example: 67 * 68 * - A producer Proc calls Proc.binOut() or Proc.textOut() to send out data. 69 * This method would prints to the stdout something like 70 * 71 * PROCISFUN:[raw text or base64 binary] 72 * 73 * - The controller calls producer.readData() to get the content. This method 74 * ignores all other output and only reads lines starting with "PROCISFUN:". 75 * 76 * - The controller does not care if the context is text or base64, it simply 77 * feeds the data to a consumer Proc by calling consumer.println(data). 78 * This will be printed into System.in of the consumer process. 79 * 80 * - The consumer Proc calls Proc.binIn() or Proc.textIn() to read the data. 81 * The first method de-base64 the input and return a byte[] block. 82 * 83 * Please note only plain ASCII is supported in raw text at the moment. 84 * 85 * As the Proc objects are hidden so deeply, two static methods, d(String) and 86 * d(Throwable) are provided to output info into stderr, where they will 87 * normally be appended messages to DFILE (unless nodump() is called). 88 * Developers can view the messages in real time by calling 89 * 90 * tail -f proc.debug 91 * 92 * TODO: 93 * 94 * . launch java tools, say, keytool 95 * . launch another version of java 96 * . start in another directory 97 * . start and finish using one method 98 * 99 * This is not a test, but is the core of 100 * JDK-8009977: A test library to launch multiple Java processes 101 */ 102 public class Proc { 103 private Process p; 104 private BufferedReader br; // the stdout of a process 105 private String launcher; // Optional: the java program 106 107 private List<Permission> perms = new ArrayList<>(); 108 private List<String> args = new ArrayList<>(); 109 private Map<String,String> env = new HashMap<>(); 110 private Map<String,String> prop = new HashMap(); 111 private boolean inheritIO = false; 112 private boolean noDump = false; 113 114 private String clazz; // Class to launch 115 private String debug; // debug flag, controller will show data 116 // transfer between procs 117 118 final private static String PREFIX = "PROCISFUN:"; 119 final private static String DFILE = "proc.debug"; 120 121 // The following methods are called by controllers 122 123 // Creates a Proc by the Java class name, launcher is an optional 124 // argument to specify the java program 125 public static Proc create(String clazz, String... launcher) { 126 Proc pc = new Proc(); 127 pc.clazz = clazz; 128 if (launcher.length > 0) { 129 pc.launcher = launcher[0]; 130 } 131 return pc; 132 } 133 // Sets inheritIO flag to proc. If set, proc will same I/O channels as 134 // teh controller. Otherwise, its stdin/stdout is untouched, and its 135 // stderr is redirected to DFILE. 136 public Proc inheritIO() { 137 inheritIO = true; 138 return this; 139 } 140 // When called, stderr inherits parent stderr, otherwise, append to a file 141 public Proc nodump() { 142 noDump = true; 143 return this; 144 } 145 // Specifies some args. Can be called multiple times. 146 public Proc args(String... args) { 147 for (String c: args) { 148 this.args.add(c); 149 } 150 return this; 151 } 152 // Returns debug prefix 153 public String debug() { 154 return debug; 155 } 156 // Enables debug with prefix 157 public Proc debug(String title) { 158 debug = title; 159 return this; 160 } 161 // Specifies an env var. Can be called multiple times. 162 public Proc env(String a, String b) { 163 env.put(a, b); 164 return this; 165 } 166 // Specifies a Java system property. Can be called multiple times. 167 public Proc prop(String a, String b) { 168 prop.put(a, b); 169 return this; 170 } 171 // Adds a perm to policy. Can be called multiple times. In order to make it 172 // effective, please also call prop("java.security.manager", ""). 173 public Proc perm(Permission p) { 174 perms.add(p); 175 return this; 176 } 177 // Starts the proc 178 public Proc start() throws IOException { 179 List<String> cmd = new ArrayList<>(); 180 if (launcher != null) { 181 cmd.add(launcher); 182 } else { 183 cmd.add(new File(new File(System.getProperty("java.home"), "bin"), 184 "java").getPath()); 185 } 186 187 int n = 0; 188 String addexports; 189 while ((addexports = System.getProperty("jdk.launcher.addexports." + n)) != null) { 190 prop("jdk.launcher.addexports." + n, addexports); 191 n++; 192 } 193 194 Collections.addAll(cmd, splitProperty("test.vm.opts")); 195 Collections.addAll(cmd, splitProperty("test.java.opts")); 196 197 cmd.add("-cp"); 198 cmd.add(System.getProperty("test.class.path") + File.pathSeparator + 199 System.getProperty("test.src.path")); 200 201 for (Entry<String,String> e: prop.entrySet()) { 202 cmd.add("-D" + e.getKey() + "=" + e.getValue()); 203 } 204 if (!perms.isEmpty()) { 205 Path p = Files.createTempFile( 206 Paths.get(".").toAbsolutePath(), "policy", null); 207 StringBuilder sb = new StringBuilder(); 208 sb.append("grant {\n"); 209 for (Permission perm: perms) { 210 // Sometimes a permission has no name or actions. 211 // but it's safe to use an empty string. 212 String s = String.format("%s \"%s\", \"%s\"", 213 perm.getClass().getCanonicalName(), 214 perm.getName() 215 .replace("\\", "\\\\").replace("\"", "\\\""), 216 perm.getActions()); 217 sb.append(" permission ").append(s).append(";\n"); 218 } 219 sb.append("};\n"); 220 Files.write(p, sb.toString().getBytes()); 221 cmd.add("-Djava.security.policy=" + p.toString()); 222 } 223 cmd.add(clazz); 224 for (String s: args) { 225 cmd.add(s); 226 } 227 if (debug != null) { 228 System.out.println("PROC: " + debug + " cmdline: " + cmd); 229 } 230 ProcessBuilder pb = new ProcessBuilder(cmd); 231 for (Entry<String,String> e: env.entrySet()) { 232 pb.environment().put(e.getKey(), e.getValue()); 233 } 234 if (inheritIO) { 235 pb.inheritIO(); 236 } else if (noDump) { 237 pb.redirectError(ProcessBuilder.Redirect.INHERIT); 238 } else { 239 pb.redirectError(ProcessBuilder.Redirect.appendTo(new File(DFILE))); 240 } 241 p = pb.start(); 242 br = new BufferedReader(new InputStreamReader(p.getInputStream())); 243 return this; 244 } 245 // Reads a line from stdout of proc 246 public String readLine() throws IOException { 247 String s = br.readLine(); 248 if (debug != null) { 249 System.out.println("PROC: " + debug + " readline: " + 250 (s == null ? "<EOF>" : s)); 251 } 252 return s; 253 } 254 // Reads a special line from stdout of proc 255 public String readData() throws Exception { 256 while (true) { 257 String s = readLine(); 258 if (s == null) { 259 if (p.waitFor() != 0) { 260 throw new Exception("Proc abnormal end"); 261 } else { 262 return s; 263 } 264 } 265 if (s.startsWith(PREFIX)) { 266 return s.substring(PREFIX.length()); 267 } 268 } 269 } 270 // Writes text into stdin of proc 271 public void println(String s) throws IOException { 272 if (debug != null) { 273 System.out.println("PROC: " + debug + " println: " + s); 274 } 275 write((s + "\n").getBytes()); 276 } 277 // Writes data into stdin of proc 278 public void write(byte[] b) throws IOException { 279 p.getOutputStream().write(b); 280 p.getOutputStream().flush(); 281 } 282 // Reads all output and wait for process end 283 public int waitFor() throws Exception { 284 while (true) { 285 String s = readLine(); 286 if (s == null) { 287 break; 288 } 289 } 290 return p.waitFor(); 291 } 292 293 // The following methods are used inside a proc 294 295 // Writes out a BASE64 binary with a prefix 296 public static void binOut(byte[] data) { 297 System.out.println(PREFIX + Base64.getEncoder().encodeToString(data)); 298 } 299 // Reads in a line of BASE64 binary 300 public static byte[] binIn() throws Exception { 301 return Base64.getDecoder().decode(textIn()); 302 } 303 // Writes out a text with a prefix 304 public static void textOut(String data) { 305 System.out.println(PREFIX + data); 306 } 307 // Reads in a line of text 308 public static String textIn() throws Exception { 309 StringBuilder sb = new StringBuilder(); 310 boolean isEmpty = true; 311 while (true) { 312 int i = System.in.read(); 313 if (i == -1) break; 314 isEmpty = false; 315 if (i == '\n') break; 316 if (i != 13) { 317 // Force it to a char, so only simple ASCII works. 318 sb.append((char)i); 319 } 320 } 321 return isEmpty ? null : sb.toString(); 322 } 323 // Sends string to stderr. If inheritIO is not called, they will 324 // be collected into DFILE 325 public static void d(String s) throws IOException { 326 System.err.println(s); 327 } 328 // Sends an exception to stderr 329 public static void d(Throwable e) throws IOException { 330 e.printStackTrace(); 331 } 332 333 private static String[] splitProperty(String prop) { 334 String s = System.getProperty(prop); 335 if (s == null || s.trim().isEmpty()) { 336 return new String[] {}; 337 } 338 return s.trim().split("\\s+"); 339 } 340 }