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.nio.file.Files; 29 import java.nio.file.Path; 30 import java.nio.file.Paths; 31 import java.security.Permission; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.Base64; 35 import java.util.Collections; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Map.Entry; 40 import java.util.stream.Stream; 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 Stream.of(jdk.internal.misc.VM.getRuntimeArguments()) 188 .filter(arg -> arg.startsWith("--add-exports=")) 189 .forEach(cmd::add); 190 191 Collections.addAll(cmd, splitProperty("test.vm.opts")); 192 Collections.addAll(cmd, splitProperty("test.java.opts")); 193 194 cmd.add("-cp"); 195 cmd.add(System.getProperty("test.class.path") + File.pathSeparator + 196 System.getProperty("test.src.path")); 197 198 for (Entry<String,String> e: prop.entrySet()) { 199 cmd.add("-D" + e.getKey() + "=" + e.getValue()); 200 } 201 if (!perms.isEmpty()) { 202 Path p = Files.createTempFile( 203 Paths.get(".").toAbsolutePath(), "policy", null); 204 StringBuilder sb = new StringBuilder(); 205 sb.append("grant {\n"); 206 for (Permission perm: perms) { 207 // Sometimes a permission has no name or actions. 208 // but it's safe to use an empty string. 209 String s = String.format("%s \"%s\", \"%s\"", 210 perm.getClass().getCanonicalName(), 211 perm.getName() 212 .replace("\\", "\\\\").replace("\"", "\\\""), 213 perm.getActions()); 214 sb.append(" permission ").append(s).append(";\n"); 215 } 216 sb.append("};\n"); 217 Files.write(p, sb.toString().getBytes()); 218 cmd.add("-Djava.security.policy=" + p.toString()); 219 } 220 cmd.add(clazz); 221 for (String s: args) { 222 cmd.add(s); 223 } 224 if (debug != null) { 225 System.out.println("PROC: " + debug + " cmdline: " + cmd); 226 } 227 ProcessBuilder pb = new ProcessBuilder(cmd); 228 for (Entry<String,String> e: env.entrySet()) { 229 pb.environment().put(e.getKey(), e.getValue()); 230 } 231 if (inheritIO) { 232 pb.inheritIO(); 233 } else if (noDump) { 234 pb.redirectError(ProcessBuilder.Redirect.INHERIT); 235 } else { 236 pb.redirectError(ProcessBuilder.Redirect.appendTo(new File(DFILE))); 237 } 238 p = pb.start(); 239 br = new BufferedReader(new InputStreamReader(p.getInputStream())); 240 return this; 241 } 242 // Reads a line from stdout of proc 243 public String readLine() throws IOException { 244 String s = br.readLine(); 245 if (debug != null) { 246 System.out.println("PROC: " + debug + " readline: " + 247 (s == null ? "<EOF>" : s)); 248 } 249 return s; 250 } 251 // Reads a special line from stdout of proc 252 public String readData() throws Exception { 253 while (true) { 254 String s = readLine(); 255 if (s == null) { 256 if (p.waitFor() != 0) { 257 throw new Exception("Proc abnormal end"); 258 } else { 259 return s; 260 } 261 } 262 if (s.startsWith(PREFIX)) { 263 return s.substring(PREFIX.length()); 264 } 265 } 266 } 267 // Writes text into stdin of proc 268 public void println(String s) throws IOException { 269 if (debug != null) { 270 System.out.println("PROC: " + debug + " println: " + s); 271 } 272 write((s + "\n").getBytes()); 273 } 274 // Writes data into stdin of proc 275 public void write(byte[] b) throws IOException { 276 p.getOutputStream().write(b); 277 p.getOutputStream().flush(); 278 } 279 // Reads all output and wait for process end 280 public int waitFor() throws Exception { 281 while (true) { 282 String s = readLine(); 283 if (s == null) { 284 break; 285 } 286 } 287 return p.waitFor(); 288 } 289 290 // The following methods are used inside a proc 291 292 // Writes out a BASE64 binary with a prefix 293 public static void binOut(byte[] data) { 294 System.out.println(PREFIX + Base64.getEncoder().encodeToString(data)); 295 } 296 // Reads in a line of BASE64 binary 297 public static byte[] binIn() throws Exception { 298 return Base64.getDecoder().decode(textIn()); 299 } 300 // Writes out a text with a prefix 301 public static void textOut(String data) { 302 System.out.println(PREFIX + data); 303 } 304 // Reads in a line of text 305 public static String textIn() throws Exception { 306 StringBuilder sb = new StringBuilder(); 307 boolean isEmpty = true; 308 while (true) { 309 int i = System.in.read(); 310 if (i == -1) break; 311 isEmpty = false; 312 if (i == '\n') break; 313 if (i != 13) { 314 // Force it to a char, so only simple ASCII works. 315 sb.append((char)i); 316 } 317 } 318 return isEmpty ? null : sb.toString(); 319 } 320 // Sends string to stderr. If inheritIO is not called, they will 321 // be collected into DFILE 322 public static void d(String s) throws IOException { 323 System.err.println(s); 324 } 325 // Sends an exception to stderr 326 public static void d(Throwable e) throws IOException { 327 e.printStackTrace(); 328 } 329 330 private static String[] splitProperty(String prop) { 331 String s = System.getProperty(prop); 332 if (s == null || s.trim().isEmpty()) { 333 return new String[] {}; 334 } 335 return s.trim().split("\\s+"); 336 } 337 }