1 /* 2 * Copyright (c) 2003, 2025, 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 * @test 26 * @bug 4199068 4738465 4937983 4930681 4926230 4931433 4932663 4986689 27 * 5026830 5023243 5070673 4052517 4811767 6192449 6397034 6413313 28 * 6464154 6523983 6206031 4960438 6631352 6631966 6850957 6850958 29 * 4947220 7018606 7034570 4244896 5049299 8003488 8054494 8058464 30 * 8067796 8224905 8263729 8265173 8272600 8231297 8282219 8285517 31 * 8352533 8368192 32 * @key intermittent 33 * @summary Basic tests for Process and Environment Variable code 34 * @modules java.base/java.lang:open 35 * java.base/java.io:open 36 * @requires !vm.musl 37 * @requires vm.flagless 38 * @library /test/lib 39 * @run main/othervm/native/timeout=360 Basic 40 * @run main/othervm/native/timeout=360 -Djdk.lang.Process.launchMechanism=fork Basic 41 * @author Martin Buchholz 42 */ 43 44 /* 45 * @test 46 * @modules java.base/java.lang:open 47 * java.base/java.io:open 48 * java.base/jdk.internal.misc 49 * @requires (os.family == "linux" & !vm.musl) 50 * @library /test/lib 51 * @run main/othervm/timeout=300 -Djdk.lang.Process.launchMechanism=posix_spawn Basic 52 */ 53 54 import java.lang.ProcessBuilder.Redirect; 55 import java.lang.ProcessHandle; 56 import static java.lang.ProcessBuilder.Redirect.*; 57 58 import java.io.*; 59 import java.nio.charset.Charset; 60 import java.nio.file.Files; 61 import java.nio.file.Path; 62 import java.nio.file.Paths; 63 import java.nio.file.StandardCopyOption; 64 import java.util.*; 65 import java.util.concurrent.CountDownLatch; 66 import java.util.concurrent.TimeUnit; 67 import java.util.regex.Pattern; 68 import java.util.regex.Matcher; 69 import static java.lang.System.getenv; 70 import static java.lang.System.out; 71 import static java.lang.Boolean.TRUE; 72 import static java.util.AbstractMap.SimpleImmutableEntry; 73 74 import jdk.test.lib.Platform; 75 76 public class Basic { 77 78 /* used for Windows only */ 79 static final String systemRoot = System.getenv("SystemRoot"); 80 81 /* used for Mac OS X only */ 82 static final String cfUserTextEncoding = System.getenv("__CF_USER_TEXT_ENCODING"); 83 84 /* used for AIX only */ 85 static final String libpath = System.getenv("LIBPATH"); 86 87 /* Used for regex String matching for long error messages */ 88 static final String PERMISSION_DENIED_ERROR_MSG = "(Permission denied|error=13)"; 89 static final String NO_SUCH_FILE_ERROR_MSG = "(No such file|error=2)"; 90 static final String SPAWNHELPER_FAILURE_MSG = "(Possible reasons:)"; 91 92 /** 93 * Returns the number of milliseconds since time given by 94 * startNanoTime, which must have been previously returned from a 95 * call to {@link System#nanoTime()}. 96 */ 97 private static long millisElapsedSince(long startNanoTime) { 98 return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanoTime); 99 } 100 101 private static String commandOutput(Reader r) throws Throwable { 102 StringBuilder sb = new StringBuilder(); 103 int c; 104 while ((c = r.read()) > 0) 105 if (c != '\r') 106 sb.append((char) c); 107 return sb.toString(); 108 } 109 110 private static String commandOutput(Process p) throws Throwable { 111 check(p.getInputStream() == p.getInputStream()); 112 check(p.getOutputStream() == p.getOutputStream()); 113 check(p.getErrorStream() == p.getErrorStream()); 114 Reader r = new InputStreamReader(p.getInputStream(),"UTF-8"); 115 String output = commandOutput(r); 116 equal(p.waitFor(), 0); 117 equal(p.exitValue(), 0); 118 // The debug/fastdebug versions of the VM may write some warnings to stdout 119 // (i.e. "Warning: Cannot open log file: hotspot.log" if the VM is started 120 // in a directory without write permissions). These warnings will confuse tests 121 // which match the entire output of the child process so better filter them out. 122 return output.replaceAll("Warning:.*\\n", ""); 123 } 124 125 private static String commandOutput(ProcessBuilder pb) { 126 try { 127 return commandOutput(pb.start()); 128 } catch (Throwable t) { 129 String commandline = ""; 130 for (String arg : pb.command()) 131 commandline += " " + arg; 132 System.out.println("Exception trying to run process: " + commandline); 133 unexpected(t); 134 return ""; 135 } 136 } 137 138 private static String commandOutput(String...command) { 139 try { 140 return commandOutput(Runtime.getRuntime().exec(command)); 141 } catch (Throwable t) { 142 String commandline = ""; 143 for (String arg : command) 144 commandline += " " + arg; 145 System.out.println("Exception trying to run process: " + commandline); 146 unexpected(t); 147 return ""; 148 } 149 } 150 151 private static void checkCommandOutput(ProcessBuilder pb, 152 String expected, 153 String failureMsg) { 154 String got = commandOutput(pb); 155 check(got.equals(expected), 156 failureMsg + "\n" + 157 "Expected: \"" + expected + "\"\n" + 158 "Got: \"" + got + "\""); 159 } 160 161 private static String absolutifyPath(String path) { 162 StringBuilder sb = new StringBuilder(); 163 for (String file : path.split(File.pathSeparator)) { 164 if (sb.length() != 0) 165 sb.append(File.pathSeparator); 166 sb.append(new File(file).getAbsolutePath()); 167 } 168 return sb.toString(); 169 } 170 171 // compare windows-style, by canonicalizing to upper case, 172 // not lower case as String.compareToIgnoreCase does 173 private static class WindowsComparator 174 implements Comparator<String> { 175 public int compare(String x, String y) { 176 return x.toUpperCase(Locale.US) 177 .compareTo(y.toUpperCase(Locale.US)); 178 } 179 } 180 181 private static String sortedLines(String lines) { 182 String[] arr = lines.split("\n"); 183 List<String> ls = new ArrayList<String>(); 184 for (String s : arr) 185 ls.add(s); 186 Collections.sort(ls, new WindowsComparator()); 187 StringBuilder sb = new StringBuilder(); 188 for (String s : ls) 189 sb.append(s + "\n"); 190 return sb.toString(); 191 } 192 193 private static void compareLinesIgnoreCase(String lines1, String lines2) { 194 if (! (sortedLines(lines1).equalsIgnoreCase(sortedLines(lines2)))) { 195 String dashes = 196 "-----------------------------------------------------"; 197 out.println(dashes); 198 out.print(sortedLines(lines1)); 199 out.println(dashes); 200 out.print(sortedLines(lines2)); 201 out.println(dashes); 202 out.println("sizes: " + sortedLines(lines1).length() + 203 " " + sortedLines(lines2).length()); 204 205 fail("Sorted string contents differ"); 206 } 207 } 208 209 private static final Runtime runtime = Runtime.getRuntime(); 210 211 private static final String[] winEnvCommand = {"cmd.exe", "/d", "/c", "set"}; 212 213 private static String winEnvFilter(String env) { 214 return env.replaceAll("\r", "") 215 .replaceAll("(?m)^(?:COMSPEC|PROMPT|PATHEXT)=.*\n",""); 216 } 217 218 private static String unixEnvProg() { 219 return new File("/usr/bin/env").canExecute() ? "/usr/bin/env" 220 : "/bin/env"; 221 } 222 223 private static String nativeEnv(String[] env) { 224 try { 225 if (Windows.is()) { 226 return winEnvFilter 227 (commandOutput(runtime.exec(winEnvCommand, env))); 228 } else { 229 return commandOutput(runtime.exec(unixEnvProg(), env)); 230 } 231 } catch (Throwable t) { throw new Error(t); } 232 } 233 234 private static String nativeEnv(ProcessBuilder pb) { 235 try { 236 if (Windows.is()) { 237 pb.command(winEnvCommand); 238 return winEnvFilter(commandOutput(pb)); 239 } else { 240 pb.command(new String[]{unixEnvProg()}); 241 return commandOutput(pb); 242 } 243 } catch (Throwable t) { throw new Error(t); } 244 } 245 246 private static void checkSizes(Map<String,String> environ, int size) { 247 try { 248 equal(size, environ.size()); 249 equal(size, environ.entrySet().size()); 250 equal(size, environ.keySet().size()); 251 equal(size, environ.values().size()); 252 253 boolean isEmpty = (size == 0); 254 equal(isEmpty, environ.isEmpty()); 255 equal(isEmpty, environ.entrySet().isEmpty()); 256 equal(isEmpty, environ.keySet().isEmpty()); 257 equal(isEmpty, environ.values().isEmpty()); 258 } catch (Throwable t) { unexpected(t); } 259 } 260 261 private interface EnvironmentFrobber { 262 void doIt(Map<String,String> environ); 263 } 264 265 private static void testVariableDeleter(EnvironmentFrobber fooDeleter) { 266 try { 267 Map<String,String> environ = new ProcessBuilder().environment(); 268 environ.put("Foo", "BAAR"); 269 fooDeleter.doIt(environ); 270 equal(environ.get("Foo"), null); 271 equal(environ.remove("Foo"), null); 272 } catch (Throwable t) { unexpected(t); } 273 } 274 275 private static void testVariableAdder(EnvironmentFrobber fooAdder) { 276 try { 277 Map<String,String> environ = new ProcessBuilder().environment(); 278 environ.remove("Foo"); 279 fooAdder.doIt(environ); 280 equal(environ.get("Foo"), "Bahrein"); 281 } catch (Throwable t) { unexpected(t); } 282 } 283 284 private static void testVariableModifier(EnvironmentFrobber fooModifier) { 285 try { 286 Map<String,String> environ = new ProcessBuilder().environment(); 287 environ.put("Foo","OldValue"); 288 fooModifier.doIt(environ); 289 equal(environ.get("Foo"), "NewValue"); 290 } catch (Throwable t) { unexpected(t); } 291 } 292 293 private static void printUTF8(String s) throws IOException { 294 out.write(s.getBytes("UTF-8")); 295 } 296 297 private static String getenvAsString(Map<String,String> environment) { 298 StringBuilder sb = new StringBuilder(); 299 environment = new TreeMap<>(environment); 300 for (Map.Entry<String,String> e : environment.entrySet()) 301 // Ignore magic environment variables added by the launcher 302 if (! e.getKey().equals("LD_LIBRARY_PATH")) 303 sb.append(e.getKey()) 304 .append('=') 305 .append(e.getValue()) 306 .append(','); 307 return sb.toString(); 308 } 309 310 static void print4095(OutputStream s, byte b) throws Throwable { 311 byte[] bytes = new byte[4095]; 312 Arrays.fill(bytes, b); 313 s.write(bytes); // Might hang! 314 } 315 316 static void checkPermissionDenied(ProcessBuilder pb) { 317 try { 318 pb.start(); 319 fail("Expected IOException not thrown"); 320 } catch (IOException e) { 321 String m = e.getMessage(); 322 if (EnglishUnix.is() && 323 !matches(m, PERMISSION_DENIED_ERROR_MSG)) 324 unexpected(e); 325 if (matches(m, SPAWNHELPER_FAILURE_MSG)) 326 unexpected(e); 327 } catch (Throwable t) { unexpected(t); } 328 } 329 330 public static class JavaChild { 331 public static void main(String args[]) throws Throwable { 332 String action = args[0]; 333 if (action.equals("sleep")) { 334 Thread.sleep(10 * 60 * 1000L); 335 } else if (action.equals("pid")) { 336 System.out.println(ProcessHandle.current().pid()); 337 } else if (action.equals("testIO")) { 338 String expected = "standard input"; 339 char[] buf = new char[expected.length()+1]; 340 int n = new InputStreamReader(System.in, System.getProperty("stdin.encoding")).read(buf,0,buf.length); 341 if (n != expected.length()) 342 System.exit(5); 343 if (! new String(buf,0,n).equals(expected)) 344 System.exit(5); 345 System.err.print("standard error"); 346 System.out.print("standard output"); 347 } else if (action.equals("testInheritIO") 348 || action.equals("testRedirectInherit")) { 349 List<String> childArgs = new ArrayList<String>(javaChildArgs); 350 childArgs.add("testIO"); 351 ProcessBuilder pb = new ProcessBuilder(childArgs); 352 if (action.equals("testInheritIO")) 353 pb.inheritIO(); 354 else 355 redirectIO(pb, INHERIT, INHERIT, INHERIT); 356 ProcessResults r = run(pb); 357 if (! r.out().equals("")) 358 System.exit(7); 359 if (! r.err().equals("")) 360 System.exit(8); 361 if (r.exitValue() != 0) 362 System.exit(9); 363 } else if (action.equals("System.getenv(String)")) { 364 String val = System.getenv(args[1]); 365 printUTF8(val == null ? "null" : val); 366 } else if (action.equals("System.getenv(\\u1234)")) { 367 String val = System.getenv("\u1234"); 368 printUTF8(val == null ? "null" : val); 369 } else if (action.equals("System.getenv()")) { 370 printUTF8(getenvAsString(System.getenv())); 371 } else if (action.equals("ArrayOOME")) { 372 Object dummy; 373 switch(new Random().nextInt(3)) { 374 case 0: dummy = new Integer[Integer.MAX_VALUE]; break; 375 case 1: dummy = new double[Integer.MAX_VALUE]; break; 376 case 2: dummy = new byte[Integer.MAX_VALUE][]; break; 377 default: throw new InternalError(); 378 } 379 } else if (action.equals("pwd")) { 380 printUTF8(new File(System.getProperty("user.dir")) 381 .getCanonicalPath()); 382 } else if (action.equals("print4095")) { 383 print4095(System.out, (byte) '!'); 384 print4095(System.err, (byte) 'E'); 385 System.exit(5); 386 } else if (action.equals("OutErr")) { 387 // You might think the system streams would be 388 // buffered, and in fact they are implemented using 389 // BufferedOutputStream, but each and every print 390 // causes immediate operating system I/O. 391 System.out.print("out"); 392 System.err.print("err"); 393 System.out.print("out"); 394 System.err.print("err"); 395 } else if (action.equals("null PATH")) { 396 equal(System.getenv("PATH"), null); 397 check(new File("/bin/true").exists()); 398 check(new File("/bin/false").exists()); 399 ProcessBuilder pb1 = new ProcessBuilder(); 400 ProcessBuilder pb2 = new ProcessBuilder(); 401 pb2.environment().put("PATH", "anyOldPathIgnoredAnyways"); 402 ProcessResults r; 403 404 for (final ProcessBuilder pb : 405 new ProcessBuilder[] {pb1, pb2}) { 406 pb.command("true"); 407 equal(run(pb).exitValue(), True.exitValue()); 408 409 pb.command("false"); 410 equal(run(pb).exitValue(), False.exitValue()); 411 } 412 413 if (failed != 0) throw new Error("null PATH"); 414 } else if (action.equals("PATH search algorithm")) { 415 equal(System.getenv("PATH"), "dir1:dir2:"); 416 check(new File(TrueExe.path()).exists()); 417 check(new File(FalseExe.path()).exists()); 418 String[] cmd = {"prog"}; 419 ProcessBuilder pb1 = new ProcessBuilder(cmd); 420 ProcessBuilder pb2 = new ProcessBuilder(cmd); 421 ProcessBuilder pb3 = new ProcessBuilder(cmd); 422 pb2.environment().put("PATH", "anyOldPathIgnoredAnyways"); 423 pb3.environment().remove("PATH"); 424 425 for (final ProcessBuilder pb : 426 new ProcessBuilder[] {pb1, pb2, pb3}) { 427 try { 428 // Not on PATH at all; directories don't exist 429 try { 430 pb.start(); 431 fail("Expected IOException not thrown"); 432 } catch (IOException e) { 433 String m = e.getMessage(); 434 if (EnglishUnix.is() && 435 !matches(m, NO_SUCH_FILE_ERROR_MSG)) 436 unexpected(e); 437 if (matches(m, SPAWNHELPER_FAILURE_MSG)) 438 unexpected(e); 439 } catch (Throwable t) { unexpected(t); } 440 441 // Not on PATH at all; directories exist 442 new File("dir1").mkdirs(); 443 new File("dir2").mkdirs(); 444 try { 445 pb.start(); 446 fail("Expected IOException not thrown"); 447 } catch (IOException e) { 448 String m = e.getMessage(); 449 if (EnglishUnix.is() && 450 !matches(m, NO_SUCH_FILE_ERROR_MSG)) 451 unexpected(e); 452 if (matches(m, SPAWNHELPER_FAILURE_MSG)) 453 unexpected(e); 454 } catch (Throwable t) { unexpected(t); } 455 456 // Can't execute a directory -- permission denied 457 // Report EACCES errno 458 new File("dir1/prog").mkdirs(); 459 checkPermissionDenied(pb); 460 461 // continue searching if EACCES 462 copy(TrueExe.path(), "dir2/prog"); 463 equal(run(pb).exitValue(), True.exitValue()); 464 new File("dir1/prog").delete(); 465 new File("dir2/prog").delete(); 466 467 new File("dir2/prog").mkdirs(); 468 copy(TrueExe.path(), "dir1/prog"); 469 equal(run(pb).exitValue(), True.exitValue()); 470 471 // Check empty PATH component means current directory. 472 // 473 // While we're here, let's test different kinds of 474 // Unix executables, and PATH vs explicit searching. 475 new File("dir1/prog").delete(); 476 new File("dir2/prog").delete(); 477 for (String[] command : 478 new String[][] { 479 new String[] {"./prog"}, 480 cmd}) { 481 pb.command(command); 482 File prog = new File("./prog"); 483 // "Normal" binaries 484 copy(TrueExe.path(), "./prog"); 485 equal(run(pb).exitValue(), 486 True.exitValue()); 487 copy(FalseExe.path(), "./prog"); 488 equal(run(pb).exitValue(), 489 False.exitValue()); 490 prog.delete(); 491 // Interpreter scripts with #! 492 setFileContents(prog, "#!/bin/true\n"); 493 prog.setExecutable(true); 494 equal(run(pb).exitValue(), 495 True.exitValue()); 496 prog.delete(); 497 setFileContents(prog, "#!/bin/false\n"); 498 prog.setExecutable(true); 499 equal(run(pb).exitValue(), 500 False.exitValue()); 501 // Traditional shell scripts without #! 502 if (!(Platform.isLinux() && Platform.isMusl())) { 503 setFileContents(prog, "exec /bin/true\n"); 504 prog.setExecutable(true); 505 equal(run(pb).exitValue(), True.exitValue()); 506 prog.delete(); 507 setFileContents(prog, "exec /bin/false\n"); 508 prog.setExecutable(true); 509 equal(run(pb).exitValue(), False.exitValue()); 510 } 511 prog.delete(); 512 } 513 514 // Test Unix interpreter scripts 515 File dir1Prog = new File("dir1/prog"); 516 dir1Prog.delete(); 517 pb.command(new String[] {"prog", "world"}); 518 setFileContents(dir1Prog, "#!/bin/echo hello\n"); 519 checkPermissionDenied(pb); 520 dir1Prog.setExecutable(true); 521 equal(run(pb).out(), "hello dir1/prog world\n"); 522 equal(run(pb).exitValue(), True.exitValue()); 523 dir1Prog.delete(); 524 pb.command(cmd); 525 526 // Test traditional shell scripts without #! 527 if (!(Platform.isLinux() && Platform.isMusl())) { 528 setFileContents(dir1Prog, "/bin/echo \"$@\"\n"); 529 pb.command(new String[] {"prog", "hello", "world"}); 530 checkPermissionDenied(pb); 531 dir1Prog.setExecutable(true); 532 equal(run(pb).out(), "hello world\n"); 533 equal(run(pb).exitValue(), True.exitValue()); 534 dir1Prog.delete(); 535 pb.command(cmd); 536 } 537 538 // If prog found on both parent and child's PATH, 539 // parent's is used. 540 new File("dir1/prog").delete(); 541 new File("dir2/prog").delete(); 542 new File("prog").delete(); 543 new File("dir3").mkdirs(); 544 copy(TrueExe.path(), "dir1/prog"); 545 copy(FalseExe.path(), "dir3/prog"); 546 pb.environment().put("PATH","dir3"); 547 equal(run(pb).exitValue(), True.exitValue()); 548 copy(TrueExe.path(), "dir3/prog"); 549 copy(FalseExe.path(), "dir1/prog"); 550 equal(run(pb).exitValue(), False.exitValue()); 551 552 } finally { 553 // cleanup 554 new File("dir1/prog").delete(); 555 new File("dir2/prog").delete(); 556 new File("dir3/prog").delete(); 557 new File("dir1").delete(); 558 new File("dir2").delete(); 559 new File("dir3").delete(); 560 new File("prog").delete(); 561 } 562 } 563 564 if (failed != 0) throw new Error("PATH search algorithm"); 565 } 566 else throw new Error("JavaChild invocation error"); 567 } 568 } 569 570 private static void copy(String src, String dst) throws IOException { 571 Files.copy(Paths.get(src), Paths.get(dst), 572 StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); 573 } 574 575 private static String javaChildOutput(ProcessBuilder pb, String...args) { 576 List<String> list = new ArrayList<String>(javaChildArgs); 577 for (String arg : args) 578 list.add(arg); 579 pb.command(list); 580 return commandOutput(pb); 581 } 582 583 private static String getenvInChild(ProcessBuilder pb) { 584 return javaChildOutput(pb, "System.getenv()"); 585 } 586 587 private static String getenvInChild1234(ProcessBuilder pb) { 588 return javaChildOutput(pb, "System.getenv(\\u1234)"); 589 } 590 591 private static String getenvInChild(ProcessBuilder pb, String name) { 592 return javaChildOutput(pb, "System.getenv(String)", name); 593 } 594 595 private static String pwdInChild(ProcessBuilder pb) { 596 return javaChildOutput(pb, "pwd"); 597 } 598 599 private static final String javaExe = 600 System.getProperty("java.home") + 601 File.separator + "bin" + File.separator + "java"; 602 603 private static final String classpath = 604 System.getProperty("java.class.path"); 605 606 private static final List<String> javaChildArgs = 607 Arrays.asList(javaExe, 608 "-XX:+DisplayVMOutputToStderr", 609 "-classpath", absolutifyPath(classpath), 610 "Basic$JavaChild"); 611 612 private static void testEncoding(String encoding, String tested) { 613 try { 614 // If round trip conversion works, should be able to set env vars 615 // correctly in child. 616 String jnuEncoding = System.getProperty("sun.jnu.encoding"); 617 Charset cs = jnuEncoding != null 618 ? Charset.forName(jnuEncoding, Charset.defaultCharset()) 619 : Charset.defaultCharset(); 620 if (new String(tested.getBytes(cs), cs).equals(tested)) { 621 out.println("Testing " + encoding + " environment values"); 622 ProcessBuilder pb = new ProcessBuilder(); 623 pb.environment().put("ASCIINAME",tested); 624 equal(getenvInChild(pb,"ASCIINAME"), tested); 625 } 626 } catch (Throwable t) { unexpected(t); } 627 } 628 629 static class Windows { 630 public static boolean is() { return is; } 631 private static final boolean is = 632 System.getProperty("os.name").startsWith("Windows"); 633 } 634 635 static class AIX { 636 public static boolean is() { return is; } 637 private static final boolean is = 638 System.getProperty("os.name").equals("AIX"); 639 } 640 641 static class Unix { 642 public static boolean is() { return is; } 643 private static final boolean is = 644 (! Windows.is() && 645 new File("/bin/sh").exists() && 646 new File("/bin/true").exists() && 647 new File("/bin/false").exists()); 648 } 649 650 static class UnicodeOS { 651 public static boolean is() { return is; } 652 private static final String osName = System.getProperty("os.name"); 653 private static final boolean is = 654 // MacOS X would probably also qualify 655 osName.startsWith("Windows") && 656 ! osName.startsWith("Windows 9") && 657 ! osName.equals("Windows Me"); 658 } 659 660 static class MacOSX { 661 public static boolean is() { return is; } 662 private static final String osName = System.getProperty("os.name"); 663 private static final boolean is = osName.contains("OS X"); 664 } 665 666 static class True { 667 public static int exitValue() { return 0; } 668 } 669 670 private static class False { 671 public static int exitValue() { return exitValue; } 672 private static final int exitValue = exitValue0(); 673 private static int exitValue0() { 674 // /bin/false returns an *unspecified* non-zero number. 675 try { 676 if (! Unix.is()) 677 return -1; 678 else { 679 int rc = new ProcessBuilder("/bin/false") 680 .start().waitFor(); 681 check(rc != 0); 682 return rc; 683 } 684 } catch (Throwable t) { unexpected(t); return -1; } 685 } 686 } 687 688 // On Alpine Linux, /bin/true and /bin/false are just links to /bin/busybox. 689 // Some tests copy /bin/true and /bin/false to files with a different filename. 690 // However, copying the busbox executable into a file with a different name 691 // won't result in the expected return codes. As workaround, we create 692 // executable files that can be copied and produce the expected return 693 // values. 694 695 private static class TrueExe { 696 public static String path() { return path; } 697 private static final String path = path0(); 698 private static String path0(){ 699 if (!Files.isSymbolicLink(Paths.get("/bin/true"))) { 700 return "/bin/true"; 701 } else { 702 File trueExe = new File("true"); 703 setFileContents(trueExe, "#!/bin/true\n"); 704 trueExe.setExecutable(true); 705 return trueExe.getAbsolutePath(); 706 } 707 } 708 } 709 710 private static class FalseExe { 711 public static String path() { return path; } 712 private static final String path = path0(); 713 private static String path0(){ 714 if (!Files.isSymbolicLink(Paths.get("/bin/false"))) { 715 return "/bin/false"; 716 } else { 717 File falseExe = new File("false"); 718 setFileContents(falseExe, "#!/bin/false\n"); 719 falseExe.setExecutable(true); 720 return falseExe.getAbsolutePath(); 721 } 722 } 723 } 724 725 static class EnglishUnix { 726 private static final Boolean is = 727 (! Windows.is() && isEnglish("LANG") && isEnglish("LC_ALL")); 728 729 private static boolean isEnglish(String envvar) { 730 String val = getenv(envvar); 731 return (val == null) || val.matches("en.*") || val.matches("C"); 732 } 733 734 /** Returns true if we can expect English OS error strings */ 735 static boolean is() { return is; } 736 } 737 738 static class DelegatingProcess extends Process { 739 final Process p; 740 741 DelegatingProcess(Process p) { 742 this.p = p; 743 } 744 745 @Override 746 public void destroy() { 747 p.destroy(); 748 } 749 750 @Override 751 public int exitValue() { 752 return p.exitValue(); 753 } 754 755 @Override 756 public int waitFor() throws InterruptedException { 757 return p.waitFor(); 758 } 759 760 @Override 761 public OutputStream getOutputStream() { 762 return p.getOutputStream(); 763 } 764 765 @Override 766 public InputStream getInputStream() { 767 return p.getInputStream(); 768 } 769 770 @Override 771 public InputStream getErrorStream() { 772 return p.getErrorStream(); 773 } 774 } 775 776 private static boolean matches(String str, String regex) { 777 return Pattern.compile(regex).matcher(str).find(); 778 } 779 780 // Return the string with the matching regex removed 781 private static String matchAndRemove(String str, String regex) { 782 return Pattern.compile(regex) 783 .matcher(str) 784 .replaceAll(""); 785 } 786 787 /* Only used for Mac OS X -- 788 * Mac OS X (may) add the variables: __CF_USER_TEXT_ENCODING, JAVA_MAIN_CLASS_<pid>, 789 * and TMPDIR. 790 * Remove them from the list of env variables 791 */ 792 private static String removeMacExpectedVars(String vars) { 793 // Check for __CF_USER_TEXT_ENCODING 794 String cleanedVars = matchAndRemove(vars, 795 "__CF_USER_TEXT_ENCODING=" + cfUserTextEncoding + ","); 796 // Check for JAVA_MAIN_CLASS_<pid> 797 cleanedVars = matchAndRemove(cleanedVars, 798 "JAVA_MAIN_CLASS_\\d+=Basic.JavaChild,"); 799 // Check and remove TMPDIR 800 cleanedVars = matchAndRemove(cleanedVars, 801 "TMPDIR=[^,]*,"); 802 return cleanedVars; 803 } 804 805 /* Only used for AIX -- 806 * AIX adds the variable AIXTHREAD_GUARDPAGES=0 to the environment. 807 * Remove it from the list of env variables 808 */ 809 private static String removeAixExpectedVars(String vars) { 810 return vars.replace("AIXTHREAD_GUARDPAGES=0,", ""); 811 } 812 813 private static String sortByLinesWindowsly(String text) { 814 String[] lines = text.split("\n"); 815 Arrays.sort(lines, new WindowsComparator()); 816 StringBuilder sb = new StringBuilder(); 817 for (String line : lines) 818 sb.append(line).append("\n"); 819 return sb.toString(); 820 } 821 822 private static void checkMapSanity(Map<String,String> map) { 823 try { 824 Set<String> keySet = map.keySet(); 825 Collection<String> values = map.values(); 826 Set<Map.Entry<String,String>> entrySet = map.entrySet(); 827 828 equal(entrySet.size(), keySet.size()); 829 equal(entrySet.size(), values.size()); 830 831 StringBuilder s1 = new StringBuilder(); 832 for (Map.Entry<String,String> e : entrySet) 833 s1.append(e.getKey() + "=" + e.getValue() + "\n"); 834 835 StringBuilder s2 = new StringBuilder(); 836 for (String var : keySet) 837 s2.append(var + "=" + map.get(var) + "\n"); 838 839 equal(s1.toString(), s2.toString()); 840 841 Iterator<String> kIter = keySet.iterator(); 842 Iterator<String> vIter = values.iterator(); 843 Iterator<Map.Entry<String,String>> eIter = entrySet.iterator(); 844 845 while (eIter.hasNext()) { 846 Map.Entry<String,String> entry = eIter.next(); 847 String key = kIter.next(); 848 String value = vIter.next(); 849 check(entrySet.contains(entry)); 850 check(keySet.contains(key)); 851 check(values.contains(value)); 852 check(map.containsKey(key)); 853 check(map.containsValue(value)); 854 equal(entry.getKey(), key); 855 equal(entry.getValue(), value); 856 } 857 check(!kIter.hasNext() && 858 !vIter.hasNext()); 859 860 } catch (Throwable t) { unexpected(t); } 861 } 862 863 private static void checkMapEquality(Map<String,String> map1, 864 Map<String,String> map2) { 865 try { 866 equal(map1.size(), map2.size()); 867 equal(map1.isEmpty(), map2.isEmpty()); 868 for (String key : map1.keySet()) { 869 equal(map1.get(key), map2.get(key)); 870 check(map2.keySet().contains(key)); 871 } 872 equal(map1, map2); 873 equal(map2, map1); 874 equal(map1.entrySet(), map2.entrySet()); 875 equal(map2.entrySet(), map1.entrySet()); 876 equal(map1.keySet(), map2.keySet()); 877 equal(map2.keySet(), map1.keySet()); 878 879 equal(map1.hashCode(), map2.hashCode()); 880 equal(map1.entrySet().hashCode(), map2.entrySet().hashCode()); 881 equal(map1.keySet().hashCode(), map2.keySet().hashCode()); 882 } catch (Throwable t) { unexpected(t); } 883 } 884 885 static void checkRedirects(ProcessBuilder pb, 886 Redirect in, Redirect out, Redirect err) { 887 equal(pb.redirectInput(), in); 888 equal(pb.redirectOutput(), out); 889 equal(pb.redirectError(), err); 890 } 891 892 static void redirectIO(ProcessBuilder pb, 893 Redirect in, Redirect out, Redirect err) { 894 pb.redirectInput(in); 895 pb.redirectOutput(out); 896 pb.redirectError(err); 897 } 898 899 static void setFileContents(File file, String contents) { 900 try { 901 Writer w = new FileWriter(file); 902 w.write(contents); 903 w.close(); 904 } catch (Throwable t) { unexpected(t); } 905 } 906 907 static String fileContents(File file) { 908 try { 909 Reader r = new FileReader(file); 910 StringBuilder sb = new StringBuilder(); 911 char[] buffer = new char[1024]; 912 int n; 913 while ((n = r.read(buffer)) != -1) 914 sb.append(buffer,0,n); 915 r.close(); 916 return new String(sb); 917 } catch (Throwable t) { unexpected(t); return ""; } 918 } 919 920 @SuppressWarnings("removal") 921 static void testIORedirection() throws Throwable { 922 final File ifile = new File("ifile"); 923 final File ofile = new File("ofile"); 924 final File efile = new File("efile"); 925 ifile.delete(); 926 ofile.delete(); 927 efile.delete(); 928 929 //---------------------------------------------------------------- 930 // Check mutual inequality of different types of Redirect 931 //---------------------------------------------------------------- 932 Redirect[] redirects = 933 { PIPE, 934 INHERIT, 935 DISCARD, 936 Redirect.from(ifile), 937 Redirect.to(ifile), 938 Redirect.appendTo(ifile), 939 Redirect.from(ofile), 940 Redirect.to(ofile), 941 Redirect.appendTo(ofile), 942 }; 943 for (int i = 0; i < redirects.length; i++) 944 for (int j = 0; j < redirects.length; j++) 945 equal(redirects[i].equals(redirects[j]), (i == j)); 946 947 //---------------------------------------------------------------- 948 // Check basic properties of different types of Redirect 949 //---------------------------------------------------------------- 950 equal(PIPE.type(), Redirect.Type.PIPE); 951 equal(PIPE.toString(), "PIPE"); 952 equal(PIPE.file(), null); 953 954 equal(INHERIT.type(), Redirect.Type.INHERIT); 955 equal(INHERIT.toString(), "INHERIT"); 956 equal(INHERIT.file(), null); 957 958 equal(DISCARD.type(), Redirect.Type.WRITE); 959 equal(DISCARD.toString(), "WRITE"); 960 equal(DISCARD.file(), new File((Windows.is() ? "NUL" : "/dev/null"))); 961 962 equal(Redirect.from(ifile).type(), Redirect.Type.READ); 963 equal(Redirect.from(ifile).toString(), 964 "redirect to read from file \"ifile\""); 965 equal(Redirect.from(ifile).file(), ifile); 966 equal(Redirect.from(ifile), 967 Redirect.from(ifile)); 968 equal(Redirect.from(ifile).hashCode(), 969 Redirect.from(ifile).hashCode()); 970 971 equal(Redirect.to(ofile).type(), Redirect.Type.WRITE); 972 equal(Redirect.to(ofile).toString(), 973 "redirect to write to file \"ofile\""); 974 equal(Redirect.to(ofile).file(), ofile); 975 equal(Redirect.to(ofile), 976 Redirect.to(ofile)); 977 equal(Redirect.to(ofile).hashCode(), 978 Redirect.to(ofile).hashCode()); 979 980 equal(Redirect.appendTo(ofile).type(), Redirect.Type.APPEND); 981 equal(Redirect.appendTo(efile).toString(), 982 "redirect to append to file \"efile\""); 983 equal(Redirect.appendTo(efile).file(), efile); 984 equal(Redirect.appendTo(efile), 985 Redirect.appendTo(efile)); 986 equal(Redirect.appendTo(efile).hashCode(), 987 Redirect.appendTo(efile).hashCode()); 988 989 //---------------------------------------------------------------- 990 // Check initial values of redirects 991 //---------------------------------------------------------------- 992 List<String> childArgs = new ArrayList<String>(javaChildArgs); 993 childArgs.add("testIO"); 994 final ProcessBuilder pb = new ProcessBuilder(childArgs); 995 checkRedirects(pb, PIPE, PIPE, PIPE); 996 997 //---------------------------------------------------------------- 998 // Check inheritIO 999 //---------------------------------------------------------------- 1000 pb.inheritIO(); 1001 checkRedirects(pb, INHERIT, INHERIT, INHERIT); 1002 1003 //---------------------------------------------------------------- 1004 // Check DISCARD for stdout,stderr 1005 //---------------------------------------------------------------- 1006 redirectIO(pb, INHERIT, DISCARD, DISCARD); 1007 checkRedirects(pb, INHERIT, DISCARD, DISCARD); 1008 1009 //---------------------------------------------------------------- 1010 // Check setters and getters agree 1011 //---------------------------------------------------------------- 1012 pb.redirectInput(ifile); 1013 equal(pb.redirectInput().file(), ifile); 1014 equal(pb.redirectInput(), Redirect.from(ifile)); 1015 1016 pb.redirectOutput(ofile); 1017 equal(pb.redirectOutput().file(), ofile); 1018 equal(pb.redirectOutput(), Redirect.to(ofile)); 1019 1020 pb.redirectError(efile); 1021 equal(pb.redirectError().file(), efile); 1022 equal(pb.redirectError(), Redirect.to(efile)); 1023 1024 THROWS(IllegalArgumentException.class, 1025 () -> pb.redirectInput(Redirect.to(ofile)), 1026 () -> pb.redirectOutput(Redirect.from(ifile)), 1027 () -> pb.redirectError(Redirect.from(ifile)), 1028 () -> pb.redirectInput(DISCARD)); 1029 1030 THROWS(NullPointerException.class, 1031 () -> pb.redirectInput((File)null), 1032 () -> pb.redirectOutput((File)null), 1033 () -> pb.redirectError((File)null), 1034 () -> pb.redirectInput((Redirect)null), 1035 () -> pb.redirectOutput((Redirect)null), 1036 () -> pb.redirectError((Redirect)null)); 1037 1038 THROWS(IOException.class, 1039 // Input file does not exist 1040 () -> pb.start()); 1041 setFileContents(ifile, "standard input"); 1042 1043 //---------------------------------------------------------------- 1044 // Writing to non-existent files 1045 //---------------------------------------------------------------- 1046 { 1047 ProcessResults r = run(pb); 1048 equal(r.exitValue(), 0); 1049 equal(fileContents(ofile), "standard output"); 1050 equal(fileContents(efile), "standard error"); 1051 equal(r.out(), ""); 1052 equal(r.err(), ""); 1053 ofile.delete(); 1054 efile.delete(); 1055 } 1056 1057 //---------------------------------------------------------------- 1058 // Both redirectErrorStream + redirectError 1059 //---------------------------------------------------------------- 1060 { 1061 pb.redirectErrorStream(true); 1062 ProcessResults r = run(pb); 1063 equal(r.exitValue(), 0); 1064 equal(fileContents(ofile), 1065 "standard error" + "standard output"); 1066 equal(fileContents(efile), ""); 1067 equal(r.out(), ""); 1068 equal(r.err(), ""); 1069 ofile.delete(); 1070 efile.delete(); 1071 } 1072 1073 //---------------------------------------------------------------- 1074 // Appending to existing files 1075 //---------------------------------------------------------------- 1076 { 1077 setFileContents(ofile, "ofile-contents"); 1078 setFileContents(efile, "efile-contents"); 1079 pb.redirectOutput(Redirect.appendTo(ofile)); 1080 pb.redirectError(Redirect.appendTo(efile)); 1081 pb.redirectErrorStream(false); 1082 ProcessResults r = run(pb); 1083 equal(r.exitValue(), 0); 1084 equal(fileContents(ofile), 1085 "ofile-contents" + "standard output"); 1086 equal(fileContents(efile), 1087 "efile-contents" + "standard error"); 1088 equal(r.out(), ""); 1089 equal(r.err(), ""); 1090 ofile.delete(); 1091 efile.delete(); 1092 } 1093 1094 //---------------------------------------------------------------- 1095 // Replacing existing files 1096 //---------------------------------------------------------------- 1097 { 1098 setFileContents(ofile, "ofile-contents"); 1099 setFileContents(efile, "efile-contents"); 1100 pb.redirectOutput(ofile); 1101 pb.redirectError(Redirect.to(efile)); 1102 ProcessResults r = run(pb); 1103 equal(r.exitValue(), 0); 1104 equal(fileContents(ofile), "standard output"); 1105 equal(fileContents(efile), "standard error"); 1106 equal(r.out(), ""); 1107 equal(r.err(), ""); 1108 ofile.delete(); 1109 efile.delete(); 1110 } 1111 1112 //---------------------------------------------------------------- 1113 // Appending twice to the same file? 1114 //---------------------------------------------------------------- 1115 { 1116 setFileContents(ofile, "ofile-contents"); 1117 setFileContents(efile, "efile-contents"); 1118 Redirect appender = Redirect.appendTo(ofile); 1119 pb.redirectOutput(appender); 1120 pb.redirectError(appender); 1121 ProcessResults r = run(pb); 1122 equal(r.exitValue(), 0); 1123 equal(fileContents(ofile), 1124 "ofile-contents" + 1125 "standard error" + 1126 "standard output"); 1127 equal(fileContents(efile), "efile-contents"); 1128 equal(r.out(), ""); 1129 equal(r.err(), ""); 1130 ifile.delete(); 1131 ofile.delete(); 1132 efile.delete(); 1133 } 1134 1135 //---------------------------------------------------------------- 1136 // DISCARDing output 1137 //---------------------------------------------------------------- 1138 { 1139 setFileContents(ifile, "standard input"); 1140 pb.redirectOutput(DISCARD); 1141 pb.redirectError(DISCARD); 1142 ProcessResults r = run(pb); 1143 equal(r.exitValue(), 0); 1144 equal(r.out(), ""); 1145 equal(r.err(), ""); 1146 } 1147 1148 //---------------------------------------------------------------- 1149 // DISCARDing output and redirecting error 1150 //---------------------------------------------------------------- 1151 { 1152 setFileContents(ifile, "standard input"); 1153 setFileContents(ofile, "ofile-contents"); 1154 setFileContents(efile, "efile-contents"); 1155 pb.redirectOutput(DISCARD); 1156 pb.redirectError(efile); 1157 ProcessResults r = run(pb); 1158 equal(r.exitValue(), 0); 1159 equal(fileContents(ofile), "ofile-contents"); 1160 equal(fileContents(efile), "standard error"); 1161 equal(r.out(), ""); 1162 equal(r.err(), ""); 1163 ofile.delete(); 1164 efile.delete(); 1165 } 1166 1167 //---------------------------------------------------------------- 1168 // DISCARDing error and redirecting output 1169 //---------------------------------------------------------------- 1170 { 1171 setFileContents(ifile, "standard input"); 1172 setFileContents(ofile, "ofile-contents"); 1173 setFileContents(efile, "efile-contents"); 1174 pb.redirectOutput(ofile); 1175 pb.redirectError(DISCARD); 1176 ProcessResults r = run(pb); 1177 equal(r.exitValue(), 0); 1178 equal(fileContents(ofile), "standard output"); 1179 equal(fileContents(efile), "efile-contents"); 1180 equal(r.out(), ""); 1181 equal(r.err(), ""); 1182 ofile.delete(); 1183 efile.delete(); 1184 } 1185 1186 //---------------------------------------------------------------- 1187 // DISCARDing output and merging error into output 1188 //---------------------------------------------------------------- 1189 { 1190 setFileContents(ifile, "standard input"); 1191 setFileContents(ofile, "ofile-contents"); 1192 setFileContents(efile, "efile-contents"); 1193 pb.redirectOutput(DISCARD); 1194 pb.redirectErrorStream(true); 1195 pb.redirectError(efile); 1196 ProcessResults r = run(pb); 1197 equal(r.exitValue(), 0); 1198 equal(fileContents(ofile), "ofile-contents"); // untouched 1199 equal(fileContents(efile), ""); // empty 1200 equal(r.out(), ""); 1201 equal(r.err(), ""); 1202 ifile.delete(); 1203 ofile.delete(); 1204 efile.delete(); 1205 pb.redirectErrorStream(false); // reset for next test 1206 } 1207 1208 //---------------------------------------------------------------- 1209 // Testing INHERIT is harder. 1210 // Note that this requires __FOUR__ nested JVMs involved in one test, 1211 // if you count the harness JVM. 1212 //---------------------------------------------------------------- 1213 for (String testName : new String[] { "testInheritIO", "testRedirectInherit" } ) { 1214 redirectIO(pb, PIPE, PIPE, PIPE); 1215 List<String> command = pb.command(); 1216 command.set(command.size() - 1, testName); 1217 Process p = pb.start(); 1218 new PrintStream(p.getOutputStream()).print("standard input"); 1219 p.getOutputStream().close(); 1220 ProcessResults r = run(p); 1221 equal(r.exitValue(), 0); 1222 equal(r.out(), "standard output"); 1223 equal(r.err(), "standard error"); 1224 } 1225 } 1226 1227 static void checkProcessPid() { 1228 ProcessBuilder pb = new ProcessBuilder(); 1229 List<String> list = new ArrayList<String>(javaChildArgs); 1230 list.add("pid"); 1231 pb.command(list); 1232 try { 1233 Process p = pb.start(); 1234 String s = commandOutput(p); 1235 long actualPid = Long.valueOf(s.trim()); 1236 long expectedPid = p.pid(); 1237 equal(actualPid, expectedPid); 1238 } catch (Throwable t) { 1239 unexpected(t); 1240 } 1241 1242 1243 // Test the default implementation of Process.getPid 1244 DelegatingProcess p = new DelegatingProcess(null); 1245 THROWS(UnsupportedOperationException.class, 1246 () -> p.pid(), 1247 () -> p.toHandle(), 1248 () -> p.supportsNormalTermination(), 1249 () -> p.children(), 1250 () -> p.descendants()); 1251 1252 } 1253 1254 @SuppressWarnings("removal") 1255 private static void realMain(String[] args) throws Throwable { 1256 if (Windows.is()) 1257 System.out.println("This appears to be a Windows system."); 1258 if (Unix.is()) 1259 System.out.println("This appears to be a Unix system."); 1260 if (UnicodeOS.is()) 1261 System.out.println("This appears to be a Unicode-based OS."); 1262 1263 try { testIORedirection(); } 1264 catch (Throwable t) { unexpected(t); } 1265 1266 //---------------------------------------------------------------- 1267 // Basic tests for getPid() 1268 //---------------------------------------------------------------- 1269 checkProcessPid(); 1270 1271 //---------------------------------------------------------------- 1272 // Basic tests for setting, replacing and deleting envvars 1273 //---------------------------------------------------------------- 1274 try { 1275 ProcessBuilder pb = new ProcessBuilder(); 1276 Map<String,String> environ = pb.environment(); 1277 1278 // New env var 1279 environ.put("QUUX", "BAR"); 1280 equal(environ.get("QUUX"), "BAR"); 1281 equal(getenvInChild(pb,"QUUX"), "BAR"); 1282 1283 // Modify env var 1284 environ.put("QUUX","bear"); 1285 equal(environ.get("QUUX"), "bear"); 1286 equal(getenvInChild(pb,"QUUX"), "bear"); 1287 checkMapSanity(environ); 1288 1289 // Remove env var 1290 environ.remove("QUUX"); 1291 equal(environ.get("QUUX"), null); 1292 equal(getenvInChild(pb,"QUUX"), "null"); 1293 checkMapSanity(environ); 1294 1295 // Remove non-existent env var 1296 environ.remove("QUUX"); 1297 equal(environ.get("QUUX"), null); 1298 equal(getenvInChild(pb,"QUUX"), "null"); 1299 checkMapSanity(environ); 1300 } catch (Throwable t) { unexpected(t); } 1301 1302 //---------------------------------------------------------------- 1303 // Pass Empty environment to child 1304 //---------------------------------------------------------------- 1305 try { 1306 ProcessBuilder pb = new ProcessBuilder(); 1307 pb.environment().clear(); 1308 String expected = Windows.is() ? "SystemRoot="+systemRoot+",": ""; 1309 expected = AIX.is() ? "LIBPATH="+libpath+",": expected; 1310 if (Windows.is()) { 1311 pb.environment().put("SystemRoot", systemRoot); 1312 } 1313 if (AIX.is()) { 1314 pb.environment().put("LIBPATH", libpath); 1315 } 1316 String result = getenvInChild(pb); 1317 if (MacOSX.is()) { 1318 result = removeMacExpectedVars(result); 1319 } 1320 if (AIX.is()) { 1321 result = removeAixExpectedVars(result); 1322 } 1323 equal(result, expected); 1324 } catch (Throwable t) { unexpected(t); } 1325 1326 //---------------------------------------------------------------- 1327 // System.getenv() is read-only. 1328 //---------------------------------------------------------------- 1329 THROWS(UnsupportedOperationException.class, 1330 () -> getenv().put("FOO","BAR"), 1331 () -> getenv().remove("PATH"), 1332 () -> getenv().keySet().remove("PATH"), 1333 () -> getenv().values().remove("someValue")); 1334 1335 try { 1336 Collection<Map.Entry<String,String>> c = getenv().entrySet(); 1337 if (! c.isEmpty()) 1338 try { 1339 c.iterator().next().setValue("foo"); 1340 fail("Expected UnsupportedOperationException not thrown"); 1341 } catch (UnsupportedOperationException e) {} // OK 1342 } catch (Throwable t) { unexpected(t); } 1343 1344 //---------------------------------------------------------------- 1345 // System.getenv() always returns the same object in our implementation. 1346 //---------------------------------------------------------------- 1347 try { 1348 check(System.getenv() == System.getenv()); 1349 } catch (Throwable t) { unexpected(t); } 1350 1351 //---------------------------------------------------------------- 1352 // You can't create an env var name containing "=", 1353 // or an env var name or value containing NUL. 1354 //---------------------------------------------------------------- 1355 { 1356 final Map<String,String> m = new ProcessBuilder().environment(); 1357 THROWS(IllegalArgumentException.class, 1358 () -> m.put("FOO=","BAR"), 1359 () -> m.put("FOO\u0000","BAR"), 1360 () -> m.put("FOO","BAR\u0000")); 1361 } 1362 1363 //---------------------------------------------------------------- 1364 // Commands must never be null. 1365 //---------------------------------------------------------------- 1366 THROWS(NullPointerException.class, 1367 () -> new ProcessBuilder((List<String>)null), 1368 () -> new ProcessBuilder().command((List<String>)null)); 1369 1370 //---------------------------------------------------------------- 1371 // Put in a command; get the same one back out. 1372 //---------------------------------------------------------------- 1373 try { 1374 List<String> command = new ArrayList<String>(); 1375 ProcessBuilder pb = new ProcessBuilder(command); 1376 check(pb.command() == command); 1377 List<String> command2 = new ArrayList<String>(2); 1378 command2.add("foo"); 1379 command2.add("bar"); 1380 pb.command(command2); 1381 check(pb.command() == command2); 1382 pb.command("foo", "bar"); 1383 check(pb.command() != command2 && pb.command().equals(command2)); 1384 pb.command(command2); 1385 command2.add("baz"); 1386 equal(pb.command().get(2), "baz"); 1387 } catch (Throwable t) { unexpected(t); } 1388 1389 //---------------------------------------------------------------- 1390 // Commands must contain at least one element. 1391 //---------------------------------------------------------------- 1392 THROWS(IndexOutOfBoundsException.class, 1393 () -> new ProcessBuilder().start(), 1394 () -> new ProcessBuilder(new ArrayList<String>()).start(), 1395 () -> Runtime.getRuntime().exec(new String[]{})); 1396 1397 //---------------------------------------------------------------- 1398 // Commands must not contain null elements at start() time. 1399 //---------------------------------------------------------------- 1400 THROWS(NullPointerException.class, 1401 () -> new ProcessBuilder("foo",null,"bar").start(), 1402 () -> new ProcessBuilder((String)null).start(), 1403 () -> new ProcessBuilder(new String[]{null}).start(), 1404 () -> new ProcessBuilder(new String[]{"foo",null,"bar"}).start()); 1405 1406 //---------------------------------------------------------------- 1407 // Command lists are growable. 1408 //---------------------------------------------------------------- 1409 try { 1410 new ProcessBuilder().command().add("foo"); 1411 new ProcessBuilder("bar").command().add("foo"); 1412 new ProcessBuilder(new String[]{"1","2"}).command().add("3"); 1413 } catch (Throwable t) { unexpected(t); } 1414 1415 //---------------------------------------------------------------- 1416 // Nulls in environment updates generate NullPointerException 1417 //---------------------------------------------------------------- 1418 try { 1419 final Map<String,String> env = new ProcessBuilder().environment(); 1420 THROWS(NullPointerException.class, 1421 () -> env.put("foo",null), 1422 () -> env.put(null,"foo"), 1423 () -> env.remove(null), 1424 () -> { for (Map.Entry<String,String> e : env.entrySet()) 1425 e.setValue(null);}, 1426 () -> Runtime.getRuntime().exec(new String[]{"foo"}, 1427 new String[]{null})); 1428 } catch (Throwable t) { unexpected(t); } 1429 1430 //---------------------------------------------------------------- 1431 // Non-String types in environment updates generate ClassCastException 1432 //---------------------------------------------------------------- 1433 try { 1434 final Map<String,String> env = new ProcessBuilder().environment(); 1435 THROWS(ClassCastException.class, 1436 () -> env.remove(TRUE), 1437 () -> env.keySet().remove(TRUE), 1438 () -> env.values().remove(TRUE), 1439 () -> env.entrySet().remove(TRUE)); 1440 } catch (Throwable t) { unexpected(t); } 1441 1442 //---------------------------------------------------------------- 1443 // Check query operations on environment maps 1444 //---------------------------------------------------------------- 1445 try { 1446 List<Map<String,String>> envs = 1447 new ArrayList<Map<String,String>>(2); 1448 envs.add(System.getenv()); 1449 envs.add(new ProcessBuilder().environment()); 1450 for (final Map<String,String> env : envs) { 1451 //---------------------------------------------------------------- 1452 // Nulls in environment queries are forbidden. 1453 //---------------------------------------------------------------- 1454 THROWS(NullPointerException.class, 1455 () -> getenv(null), 1456 () -> env.get(null), 1457 () -> env.containsKey(null), 1458 () -> env.containsValue(null), 1459 () -> env.keySet().contains(null), 1460 () -> env.values().contains(null)); 1461 1462 //---------------------------------------------------------------- 1463 // Non-String types in environment queries are forbidden. 1464 //---------------------------------------------------------------- 1465 THROWS(ClassCastException.class, 1466 () -> env.get(TRUE), 1467 () -> env.containsKey(TRUE), 1468 () -> env.containsValue(TRUE), 1469 () -> env.keySet().contains(TRUE), 1470 () -> env.values().contains(TRUE)); 1471 1472 //---------------------------------------------------------------- 1473 // Illegal String values in environment queries are (grumble) OK 1474 //---------------------------------------------------------------- 1475 equal(env.get("\u0000"), null); 1476 check(! env.containsKey("\u0000")); 1477 check(! env.containsValue("\u0000")); 1478 check(! env.keySet().contains("\u0000")); 1479 check(! env.values().contains("\u0000")); 1480 } 1481 1482 } catch (Throwable t) { unexpected(t); } 1483 1484 try { 1485 final Set<Map.Entry<String,String>> entrySet = 1486 new ProcessBuilder().environment().entrySet(); 1487 THROWS(NullPointerException.class, 1488 () -> entrySet.contains(null)); 1489 THROWS(ClassCastException.class, 1490 () -> entrySet.contains(TRUE), 1491 () -> entrySet.contains( 1492 new SimpleImmutableEntry<Boolean,String>(TRUE,""))); 1493 1494 check(! entrySet.contains 1495 (new SimpleImmutableEntry<String,String>("", ""))); 1496 } catch (Throwable t) { unexpected(t); } 1497 1498 //---------------------------------------------------------------- 1499 // Put in a directory; get the same one back out. 1500 //---------------------------------------------------------------- 1501 try { 1502 ProcessBuilder pb = new ProcessBuilder(); 1503 File foo = new File("foo"); 1504 equal(pb.directory(), null); 1505 equal(pb.directory(foo).directory(), foo); 1506 equal(pb.directory(null).directory(), null); 1507 } catch (Throwable t) { unexpected(t); } 1508 1509 //---------------------------------------------------------------- 1510 // If round-trip conversion works, check envvar pass-through to child 1511 //---------------------------------------------------------------- 1512 try { 1513 testEncoding("ASCII", "xyzzy"); 1514 testEncoding("Latin1", "\u00f1\u00e1"); 1515 testEncoding("Unicode", "\u22f1\u11e1"); 1516 } catch (Throwable t) { unexpected(t); } 1517 1518 //---------------------------------------------------------------- 1519 // A surprisingly large number of ways to delete an environment var. 1520 //---------------------------------------------------------------- 1521 testVariableDeleter(new EnvironmentFrobber() { 1522 public void doIt(Map<String,String> environ) { 1523 environ.remove("Foo");}}); 1524 1525 testVariableDeleter(new EnvironmentFrobber() { 1526 public void doIt(Map<String,String> environ) { 1527 environ.keySet().remove("Foo");}}); 1528 1529 testVariableDeleter(new EnvironmentFrobber() { 1530 public void doIt(Map<String,String> environ) { 1531 environ.values().remove("BAAR");}}); 1532 1533 testVariableDeleter(new EnvironmentFrobber() { 1534 public void doIt(Map<String,String> environ) { 1535 // Legally fabricate a ProcessEnvironment.StringEntry, 1536 // even though it's private. 1537 Map<String,String> environ2 1538 = new ProcessBuilder().environment(); 1539 environ2.clear(); 1540 environ2.put("Foo","BAAR"); 1541 // Subtlety alert. 1542 Map.Entry<String,String> e 1543 = environ2.entrySet().iterator().next(); 1544 environ.entrySet().remove(e);}}); 1545 1546 testVariableDeleter(new EnvironmentFrobber() { 1547 public void doIt(Map<String,String> environ) { 1548 Map.Entry<String,String> victim = null; 1549 for (Map.Entry<String,String> e : environ.entrySet()) 1550 if (e.getKey().equals("Foo")) 1551 victim = e; 1552 if (victim != null) 1553 environ.entrySet().remove(victim);}}); 1554 1555 testVariableDeleter(new EnvironmentFrobber() { 1556 public void doIt(Map<String,String> environ) { 1557 Iterator<String> it = environ.keySet().iterator(); 1558 while (it.hasNext()) { 1559 String val = it.next(); 1560 if (val.equals("Foo")) 1561 it.remove();}}}); 1562 1563 testVariableDeleter(new EnvironmentFrobber() { 1564 public void doIt(Map<String,String> environ) { 1565 Iterator<Map.Entry<String,String>> it 1566 = environ.entrySet().iterator(); 1567 while (it.hasNext()) { 1568 Map.Entry<String,String> e = it.next(); 1569 if (e.getKey().equals("Foo")) 1570 it.remove();}}}); 1571 1572 testVariableDeleter(new EnvironmentFrobber() { 1573 public void doIt(Map<String,String> environ) { 1574 Iterator<String> it = environ.values().iterator(); 1575 while (it.hasNext()) { 1576 String val = it.next(); 1577 if (val.equals("BAAR")) 1578 it.remove();}}}); 1579 1580 //---------------------------------------------------------------- 1581 // A surprisingly small number of ways to add an environment var. 1582 //---------------------------------------------------------------- 1583 testVariableAdder(new EnvironmentFrobber() { 1584 public void doIt(Map<String,String> environ) { 1585 environ.put("Foo","Bahrein");}}); 1586 1587 //---------------------------------------------------------------- 1588 // A few ways to modify an environment var. 1589 //---------------------------------------------------------------- 1590 testVariableModifier(new EnvironmentFrobber() { 1591 public void doIt(Map<String,String> environ) { 1592 environ.put("Foo","NewValue");}}); 1593 1594 testVariableModifier(new EnvironmentFrobber() { 1595 public void doIt(Map<String,String> environ) { 1596 for (Map.Entry<String,String> e : environ.entrySet()) 1597 if (e.getKey().equals("Foo")) 1598 e.setValue("NewValue");}}); 1599 1600 //---------------------------------------------------------------- 1601 // Fiddle with environment sizes 1602 //---------------------------------------------------------------- 1603 try { 1604 Map<String,String> environ = new ProcessBuilder().environment(); 1605 int size = environ.size(); 1606 checkSizes(environ, size); 1607 1608 environ.put("UnLiKeLYeNVIROmtNam", "someVal"); 1609 checkSizes(environ, size+1); 1610 1611 // Check for environment independence 1612 new ProcessBuilder().environment().clear(); 1613 1614 environ.put("UnLiKeLYeNVIROmtNam", "someOtherVal"); 1615 checkSizes(environ, size+1); 1616 1617 environ.remove("UnLiKeLYeNVIROmtNam"); 1618 checkSizes(environ, size); 1619 1620 environ.clear(); 1621 checkSizes(environ, 0); 1622 1623 environ.clear(); 1624 checkSizes(environ, 0); 1625 1626 environ = new ProcessBuilder().environment(); 1627 environ.keySet().clear(); 1628 checkSizes(environ, 0); 1629 1630 environ = new ProcessBuilder().environment(); 1631 environ.entrySet().clear(); 1632 checkSizes(environ, 0); 1633 1634 environ = new ProcessBuilder().environment(); 1635 environ.values().clear(); 1636 checkSizes(environ, 0); 1637 } catch (Throwable t) { unexpected(t); } 1638 1639 //---------------------------------------------------------------- 1640 // Check that various map invariants hold 1641 //---------------------------------------------------------------- 1642 checkMapSanity(new ProcessBuilder().environment()); 1643 checkMapSanity(System.getenv()); 1644 checkMapEquality(new ProcessBuilder().environment(), 1645 new ProcessBuilder().environment()); 1646 1647 1648 //---------------------------------------------------------------- 1649 // Check effects on external "env" command. 1650 //---------------------------------------------------------------- 1651 try { 1652 Set<String> env1 = new HashSet<String> 1653 (Arrays.asList(nativeEnv((String[])null).split("\n"))); 1654 1655 ProcessBuilder pb = new ProcessBuilder(); 1656 pb.environment().put("QwErTyUiOp","AsDfGhJk"); 1657 1658 Set<String> env2 = new HashSet<String> 1659 (Arrays.asList(nativeEnv(pb).split("\n"))); 1660 1661 check(env2.size() == env1.size() + 1); 1662 env1.add("QwErTyUiOp=AsDfGhJk"); 1663 check(env1.equals(env2)); 1664 } catch (Throwable t) { unexpected(t); } 1665 1666 //---------------------------------------------------------------- 1667 // Test Runtime.exec(...envp...) 1668 // Check for sort order of environment variables on Windows. 1669 //---------------------------------------------------------------- 1670 try { 1671 String systemRoot = "SystemRoot=" + System.getenv("SystemRoot"); 1672 // '+' < 'A' < 'Z' < '_' < 'a' < 'z' < '~' 1673 String[]envp = {"FOO=BAR","BAZ=GORP","QUUX=", 1674 "+=+", "_=_", "~=~", systemRoot}; 1675 String output = nativeEnv(envp); 1676 String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\n"+systemRoot+"\n_=_\n~=~\n"; 1677 // On Windows, Java must keep the environment sorted. 1678 // Order is random on Unix, so this test does the sort. 1679 if (! Windows.is()) 1680 output = sortByLinesWindowsly(output); 1681 equal(output, expected); 1682 } catch (Throwable t) { unexpected(t); } 1683 1684 //---------------------------------------------------------------- 1685 // Test Runtime.exec(...envp...) 1686 // and check SystemRoot gets set automatically on Windows 1687 //---------------------------------------------------------------- 1688 try { 1689 if (Windows.is()) { 1690 String systemRoot = "SystemRoot=" + System.getenv("SystemRoot"); 1691 String[]envp = {"FOO=BAR","BAZ=GORP","QUUX=", 1692 "+=+", "_=_", "~=~"}; 1693 String output = nativeEnv(envp); 1694 String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\n"+systemRoot+"\n_=_\n~=~\n"; 1695 equal(output, expected); 1696 } 1697 } catch (Throwable t) { unexpected(t); } 1698 1699 //---------------------------------------------------------------- 1700 // System.getenv() must be consistent with System.getenv(String) 1701 //---------------------------------------------------------------- 1702 try { 1703 for (Map.Entry<String,String> e : getenv().entrySet()) 1704 equal(getenv(e.getKey()), e.getValue()); 1705 } catch (Throwable t) { unexpected(t); } 1706 1707 //---------------------------------------------------------------- 1708 // Fiddle with working directory in child 1709 //---------------------------------------------------------------- 1710 try { 1711 String canonicalUserDir = 1712 new File(System.getProperty("user.dir")).getCanonicalPath(); 1713 String[] sdirs = new String[] 1714 {".", "..", "/", "/bin", 1715 "C:", "c:", "C:/", "c:\\", "\\", "\\bin", 1716 "c:\\windows ", "c:\\Program Files", "c:\\Program Files\\" }; 1717 for (String sdir : sdirs) { 1718 File dir = new File(sdir); 1719 if (! (dir.isDirectory() && dir.exists())) 1720 continue; 1721 out.println("Testing directory " + dir); 1722 //dir = new File(dir.getCanonicalPath()); 1723 1724 ProcessBuilder pb = new ProcessBuilder(); 1725 equal(pb.directory(), null); 1726 equal(pwdInChild(pb), canonicalUserDir); 1727 1728 pb.directory(dir); 1729 equal(pb.directory(), dir); 1730 equal(pwdInChild(pb), dir.getCanonicalPath()); 1731 1732 pb.directory(null); 1733 equal(pb.directory(), null); 1734 equal(pwdInChild(pb), canonicalUserDir); 1735 1736 pb.directory(dir); 1737 } 1738 } catch (Throwable t) { unexpected(t); } 1739 1740 //---------------------------------------------------------------- 1741 // Working directory with Unicode in child 1742 //---------------------------------------------------------------- 1743 try { 1744 if (UnicodeOS.is()) { 1745 File dir = new File(System.getProperty("test.dir", "."), 1746 "ProcessBuilderDir\u4e00\u4e02"); 1747 try { 1748 if (!dir.exists()) 1749 dir.mkdir(); 1750 out.println("Testing Unicode directory:" + dir); 1751 ProcessBuilder pb = new ProcessBuilder(); 1752 pb.directory(dir); 1753 equal(pwdInChild(pb), dir.getCanonicalPath()); 1754 } finally { 1755 if (dir.exists()) 1756 dir.delete(); 1757 } 1758 } 1759 } catch (Throwable t) { unexpected(t); } 1760 1761 //---------------------------------------------------------------- 1762 // OOME in child allocating maximally sized array 1763 // Test for hotspot/jvmti bug 6850957 1764 //---------------------------------------------------------------- 1765 try { 1766 List<String> list = new ArrayList<String>(javaChildArgs); 1767 list.add(1, String.format("-XX:OnOutOfMemoryError=%s -version", 1768 javaExe)); 1769 list.add("ArrayOOME"); 1770 ProcessResults r = run(new ProcessBuilder(list)); 1771 check(r.err().contains("java.lang.OutOfMemoryError:")); 1772 check(r.err().contains(javaExe)); 1773 check(r.err().contains(System.getProperty("java.version"))); 1774 equal(r.exitValue(), 1); 1775 } catch (Throwable t) { unexpected(t); } 1776 1777 //---------------------------------------------------------------- 1778 // Windows has tricky semi-case-insensitive semantics 1779 //---------------------------------------------------------------- 1780 if (Windows.is()) 1781 try { 1782 out.println("Running case insensitve variable tests"); 1783 for (String[] namePair : 1784 new String[][] 1785 { new String[]{"PATH","PaTh"}, 1786 new String[]{"home","HOME"}, 1787 new String[]{"SYSTEMROOT","SystemRoot"}}) { 1788 check((getenv(namePair[0]) == null && 1789 getenv(namePair[1]) == null) 1790 || 1791 getenv(namePair[0]).equals(getenv(namePair[1])), 1792 "Windows environment variables are not case insensitive"); 1793 } 1794 } catch (Throwable t) { unexpected(t); } 1795 1796 //---------------------------------------------------------------- 1797 // Test proper Unicode child environment transfer 1798 //---------------------------------------------------------------- 1799 if (UnicodeOS.is()) 1800 try { 1801 ProcessBuilder pb = new ProcessBuilder(); 1802 pb.environment().put("\u1234","\u5678"); 1803 pb.environment().remove("PATH"); 1804 equal(getenvInChild1234(pb), "\u5678"); 1805 } catch (Throwable t) { unexpected(t); } 1806 1807 1808 //---------------------------------------------------------------- 1809 // Test Runtime.exec(...envp...) with envstrings with initial `=' 1810 //---------------------------------------------------------------- 1811 try { 1812 List<String> childArgs = new ArrayList<String>(javaChildArgs); 1813 childArgs.add("System.getenv()"); 1814 String[] cmdp = childArgs.toArray(new String[childArgs.size()]); 1815 String[] envp; 1816 String[] envpWin = {"=C:=\\", "=ExitValue=3", "SystemRoot="+systemRoot}; 1817 String[] envpOth = {"=ExitValue=3", "=C:=\\"}; 1818 if (Windows.is()) { 1819 envp = envpWin; 1820 } else if (AIX.is()) { 1821 envp = new String[] {"=ExitValue=3", "=C:=\\", "LIBPATH=" + libpath}; 1822 } else { 1823 envp = envpOth; 1824 } 1825 Process p = Runtime.getRuntime().exec(cmdp, envp); 1826 String expected = Windows.is() ? "=C:=\\,=ExitValue=3,SystemRoot="+systemRoot+"," : "=C:=\\,"; 1827 expected = AIX.is() ? expected + "LIBPATH="+libpath+",": expected; 1828 String commandOutput = commandOutput(p); 1829 if (MacOSX.is()) { 1830 commandOutput = removeMacExpectedVars(commandOutput); 1831 } 1832 if (AIX.is()) { 1833 commandOutput = removeAixExpectedVars(commandOutput); 1834 } 1835 equal(commandOutput, expected); 1836 if (Windows.is()) { 1837 ProcessBuilder pb = new ProcessBuilder(childArgs); 1838 pb.environment().clear(); 1839 pb.environment().put("SystemRoot", systemRoot); 1840 pb.environment().put("=ExitValue", "3"); 1841 pb.environment().put("=C:", "\\"); 1842 equal(commandOutput(pb), expected); 1843 } 1844 } catch (Throwable t) { unexpected(t); } 1845 1846 //---------------------------------------------------------------- 1847 // Test Runtime.exec(...envp...) with envstrings without any `=' 1848 //---------------------------------------------------------------- 1849 try { 1850 // In Windows CMD (`cmd.exe`), `echo/` outputs a newline (i.e., an empty line). 1851 // Wrapping it with `cmd.exe /c` ensures compatibility in both native Windows and Cygwin environments. 1852 String[] cmdp = Windows.is() ? new String[]{"cmd.exe", "/c", "echo/"} : new String[]{"echo"}; 1853 String[] envp = {"Hello", "World"}; // Yuck! 1854 Process p = Runtime.getRuntime().exec(cmdp, envp); 1855 equal(commandOutput(p), "\n"); 1856 } catch (Throwable t) { unexpected(t); } 1857 1858 //---------------------------------------------------------------- 1859 // Test Runtime.exec(...envp...) with envstrings containing NULs 1860 //---------------------------------------------------------------- 1861 try { 1862 List<String> childArgs = new ArrayList<String>(javaChildArgs); 1863 childArgs.add("System.getenv()"); 1864 String[] cmdp = childArgs.toArray(new String[childArgs.size()]); 1865 String[] envpWin = {"SystemRoot="+systemRoot, "LC_ALL=C\u0000\u0000", // Yuck! 1866 "FO\u0000=B\u0000R"}; 1867 String[] envpOth = {"LC_ALL=C\u0000\u0000", // Yuck! 1868 "FO\u0000=B\u0000R"}; 1869 String[] envp; 1870 if (Windows.is()) { 1871 envp = envpWin; 1872 } else if (AIX.is()) { 1873 envp = new String[] {"LC_ALL=C\u0000\u0000", // Yuck! 1874 "FO\u0000=B\u0000R", "LIBPATH=" + libpath}; 1875 } else { 1876 envp = envpOth; 1877 } 1878 System.out.println ("cmdp"); 1879 for (int i=0; i<cmdp.length; i++) { 1880 System.out.printf ("cmdp %d: %s\n", i, cmdp[i]); 1881 } 1882 System.out.println ("envp"); 1883 for (int i=0; i<envp.length; i++) { 1884 System.out.printf ("envp %d: %s\n", i, envp[i]); 1885 } 1886 Process p = Runtime.getRuntime().exec(cmdp, envp); 1887 String commandOutput = commandOutput(p); 1888 if (MacOSX.is()) { 1889 commandOutput = removeMacExpectedVars(commandOutput); 1890 } 1891 if (AIX.is()) { 1892 commandOutput = removeAixExpectedVars(commandOutput); 1893 } 1894 check(commandOutput.equals(Windows.is() 1895 ? "LC_ALL=C,SystemRoot="+systemRoot+"," 1896 : AIX.is() 1897 ? "LC_ALL=C,LIBPATH="+libpath+"," 1898 : "LC_ALL=C,"), 1899 "Incorrect handling of envstrings containing NULs"); 1900 } catch (Throwable t) { unexpected(t); } 1901 1902 //---------------------------------------------------------------- 1903 // Test the redirectErrorStream property 1904 //---------------------------------------------------------------- 1905 try { 1906 ProcessBuilder pb = new ProcessBuilder(); 1907 equal(pb.redirectErrorStream(), false); 1908 equal(pb.redirectErrorStream(true), pb); 1909 equal(pb.redirectErrorStream(), true); 1910 equal(pb.redirectErrorStream(false), pb); 1911 equal(pb.redirectErrorStream(), false); 1912 } catch (Throwable t) { unexpected(t); } 1913 1914 try { 1915 List<String> childArgs = new ArrayList<String>(javaChildArgs); 1916 childArgs.add("OutErr"); 1917 ProcessBuilder pb = new ProcessBuilder(childArgs); 1918 { 1919 ProcessResults r = run(pb); 1920 equal(r.out(), "outout"); 1921 equal(r.err(), "errerr"); 1922 } 1923 { 1924 pb.redirectErrorStream(true); 1925 ProcessResults r = run(pb); 1926 equal(r.out(), "outerrouterr"); 1927 equal(r.err(), ""); 1928 } 1929 } catch (Throwable t) { unexpected(t); } 1930 1931 if (Unix.is()) { 1932 //---------------------------------------------------------------- 1933 // We can find true and false when PATH is null 1934 //---------------------------------------------------------------- 1935 try { 1936 List<String> childArgs = new ArrayList<String>(javaChildArgs); 1937 childArgs.add("null PATH"); 1938 ProcessBuilder pb = new ProcessBuilder(childArgs); 1939 pb.environment().remove("PATH"); 1940 ProcessResults r = run(pb); 1941 equal(r.out(), ""); 1942 equal(r.err(), ""); 1943 equal(r.exitValue(), 0); 1944 } catch (Throwable t) { unexpected(t); } 1945 1946 //---------------------------------------------------------------- 1947 // PATH search algorithm on Unix 1948 //---------------------------------------------------------------- 1949 try { 1950 List<String> childArgs = new ArrayList<String>(javaChildArgs); 1951 childArgs.add("PATH search algorithm"); 1952 ProcessBuilder pb = new ProcessBuilder(childArgs); 1953 pb.environment().put("PATH", "dir1:dir2:"); 1954 ProcessResults r = run(pb); 1955 equal(r.out(), ""); 1956 equal(r.err(), ""); 1957 equal(r.exitValue(), True.exitValue()); 1958 } catch (Throwable t) { unexpected(t); } 1959 1960 //---------------------------------------------------------------- 1961 // Parent's, not child's PATH is used 1962 //---------------------------------------------------------------- 1963 try { 1964 new File("suBdiR").mkdirs(); 1965 copy(TrueExe.path(), "suBdiR/unliKely"); 1966 final ProcessBuilder pb = 1967 new ProcessBuilder(new String[]{"unliKely"}); 1968 pb.environment().put("PATH", "suBdiR"); 1969 THROWS(IOException.class, () -> pb.start()); 1970 } catch (Throwable t) { unexpected(t); 1971 } finally { 1972 new File("suBdiR/unliKely").delete(); 1973 new File("suBdiR").delete(); 1974 } 1975 } 1976 1977 //---------------------------------------------------------------- 1978 // Attempt to start bogus program "" 1979 //---------------------------------------------------------------- 1980 try { 1981 new ProcessBuilder("").start(); 1982 fail("Expected IOException not thrown"); 1983 } catch (IOException e) { 1984 String m = e.getMessage(); 1985 if (EnglishUnix.is() && 1986 ! matches(m, NO_SUCH_FILE_ERROR_MSG)) 1987 unexpected(e); 1988 if (matches(m, SPAWNHELPER_FAILURE_MSG)) 1989 unexpected(e); 1990 } catch (Throwable t) { unexpected(t); } 1991 1992 //---------------------------------------------------------------- 1993 // Check that attempt to execute program name with funny 1994 // characters throws an exception containing those characters. 1995 //---------------------------------------------------------------- 1996 for (String programName : new String[] {"\u00f0", "\u01f0"}) 1997 try { 1998 new ProcessBuilder(programName).start(); 1999 fail("Expected IOException not thrown"); 2000 } catch (IOException e) { 2001 String m = e.getMessage(); 2002 Pattern p = Pattern.compile(programName); 2003 if (! matches(m, programName) 2004 || (EnglishUnix.is() && 2005 ! matches(m, NO_SUCH_FILE_ERROR_MSG))) 2006 unexpected(e); 2007 if (matches(m, SPAWNHELPER_FAILURE_MSG)) 2008 unexpected(e); 2009 } catch (Throwable t) { unexpected(t); } 2010 2011 //---------------------------------------------------------------- 2012 // Attempt to start process in nonexistent directory fails. 2013 //---------------------------------------------------------------- 2014 try { 2015 new ProcessBuilder("echo") 2016 .directory(new File("UnLiKeLY")) 2017 .start(); 2018 fail("Expected IOException not thrown"); 2019 } catch (IOException e) { 2020 String m = e.getMessage(); 2021 if (! matches(m, "in directory") 2022 || (EnglishUnix.is() && 2023 ! matches(m, NO_SUCH_FILE_ERROR_MSG))) 2024 unexpected(e); 2025 if (matches(m, SPAWNHELPER_FAILURE_MSG)) 2026 unexpected(e); 2027 } catch (Throwable t) { unexpected(t); } 2028 2029 //---------------------------------------------------------------- 2030 // Attempt to write 4095 bytes to the pipe buffer without a 2031 // reader to drain it would deadlock, if not for the fact that 2032 // interprocess pipe buffers are at least 4096 bytes. 2033 // 2034 // Also, check that available reports all the bytes expected 2035 // in the pipe buffer, and that I/O operations do the expected 2036 // things. 2037 //---------------------------------------------------------------- 2038 try { 2039 List<String> childArgs = new ArrayList<String>(javaChildArgs); 2040 childArgs.add("print4095"); 2041 final int SIZE = 4095; 2042 final Process p = new ProcessBuilder(childArgs).start(); 2043 print4095(p.getOutputStream(), (byte) '!'); // Might hang! 2044 p.waitFor(); // Might hang! 2045 equal(SIZE, p.getInputStream().available()); 2046 equal(SIZE, p.getErrorStream().available()); 2047 THROWS(IOException.class, 2048 () -> { p.getOutputStream().write((byte) '!'); 2049 p.getOutputStream().flush();}); 2050 2051 final byte[] bytes = new byte[SIZE + 1]; 2052 equal(SIZE, p.getInputStream().read(bytes)); 2053 for (int i = 0; i < SIZE; i++) 2054 equal((byte) '!', bytes[i]); 2055 equal((byte) 0, bytes[SIZE]); 2056 2057 equal(SIZE, p.getErrorStream().read(bytes)); 2058 for (int i = 0; i < SIZE; i++) 2059 equal((byte) 'E', bytes[i]); 2060 equal((byte) 0, bytes[SIZE]); 2061 2062 equal(0, p.getInputStream().available()); 2063 equal(0, p.getErrorStream().available()); 2064 equal(-1, p.getErrorStream().read()); 2065 equal(-1, p.getInputStream().read()); 2066 2067 equal(p.exitValue(), 5); 2068 2069 p.getInputStream().close(); 2070 p.getErrorStream().close(); 2071 try { p.getOutputStream().close(); } catch (IOException flushFailed) { } 2072 2073 InputStream[] streams = { p.getInputStream(), p.getErrorStream() }; 2074 for (final InputStream in : streams) { 2075 Fun[] ops = { 2076 () -> in.read(), 2077 () -> in.read(bytes), 2078 () -> in.available() 2079 }; 2080 for (Fun op : ops) { 2081 try { 2082 op.f(); 2083 fail(); 2084 } catch (IOException expected) { 2085 String m = expected.getMessage(); 2086 check(m.matches("[Ss]tream [Cc]losed")); 2087 check(!matches(m, SPAWNHELPER_FAILURE_MSG)); 2088 } 2089 } 2090 } 2091 } catch (Throwable t) { unexpected(t); } 2092 2093 //---------------------------------------------------------------- 2094 // Check that reads which are pending when Process.destroy is 2095 // called, get EOF, or IOException("Stream closed"). 2096 //---------------------------------------------------------------- 2097 try { 2098 final int cases = 4; 2099 for (int i = 0; i < cases; i++) { 2100 final int action = i; 2101 List<String> childArgs = getSleepArgs(); 2102 final ProcessBuilder pb = new ProcessBuilder(childArgs); 2103 final byte[] bytes = new byte[10]; 2104 final Process p = pb.start(); 2105 final CountDownLatch latch = new CountDownLatch(1); 2106 final InputStream s; 2107 switch (action & 0x1) { 2108 case 0: s = p.getInputStream(); break; 2109 case 1: s = p.getErrorStream(); break; 2110 default: throw new Error(); 2111 } 2112 final Thread thread = new Thread() { 2113 public void run() { 2114 try { 2115 int r; 2116 latch.countDown(); 2117 switch (action & 0x2) { 2118 case 0: r = s.read(); break; 2119 case 2: r = s.read(bytes); break; 2120 default: throw new Error(); 2121 } 2122 if (r >= 0) { 2123 // The child sent unexpected output; print it to diagnose 2124 System.out.println("Unexpected child output, to: " + 2125 ((action & 0x1) == 0 ? "getInputStream" : "getErrorStream")); 2126 System.out.println("Child args: " + childArgs); 2127 if ((action & 0x2) == 0) { 2128 System.out.write(r); // Single character 2129 2130 } else { 2131 System.out.write(bytes, 0, r); 2132 } 2133 for (int c = s.read(); c >= 0; c = s.read()) 2134 System.out.write(c); 2135 System.out.println("\nEND Child output."); 2136 } 2137 equal(-1, r); 2138 } catch (IOException ioe) { 2139 String m = ioe.getMessage(); 2140 if (!m.equals("Stream closed")) { 2141 // BufferedInputStream may throw IOE("Stream closed"). 2142 unexpected(ioe); 2143 } 2144 if (matches(m, SPAWNHELPER_FAILURE_MSG)) { 2145 unexpected(ioe); 2146 } 2147 } catch (Throwable t) { unexpected(t); }}}; 2148 2149 thread.start(); 2150 latch.await(); 2151 Thread.sleep(30); 2152 2153 if (s instanceof BufferedInputStream) { 2154 // Wait until after the s.read occurs in "thread" by 2155 // checking when the input stream monitor is acquired 2156 // (BufferedInputStream.read is synchronized) 2157 while (!isLocked((BufferedInputStream) s)) { 2158 Thread.sleep(100); 2159 } 2160 } 2161 p.destroy(); 2162 thread.join(); 2163 } 2164 } catch (Throwable t) { unexpected(t); } 2165 2166 //---------------------------------------------------------------- 2167 // Check that subprocesses which create subprocesses of their 2168 // own do not cause parent to hang waiting for file 2169 // descriptors to be closed. 2170 //---------------------------------------------------------------- 2171 try { 2172 if (Unix.is() 2173 && new File("/bin/bash").exists() 2174 && new File("/bin/sleep").exists()) { 2175 // Notice that we only destroy the process created by us (i.e. 2176 // our child) but not our grandchild (i.e. '/bin/sleep'). So 2177 // pay attention that the grandchild doesn't run too long to 2178 // avoid polluting the process space with useless processes. 2179 // Running the grandchild for 59s should be more than enough. 2180 // A unique (59s) time is needed to avoid killing other sleep processes. 2181 final String[] cmd = { "/bin/bash", "-c", "(/bin/sleep 59)" }; 2182 final String[] cmdkill = { "/bin/bash", "-c", "(/usr/bin/pkill -f \"sleep 59\")" }; 2183 final ProcessBuilder pb = new ProcessBuilder(cmd); 2184 final Process p = pb.start(); 2185 final InputStream stdout = p.getInputStream(); 2186 final InputStream stderr = p.getErrorStream(); 2187 final OutputStream stdin = p.getOutputStream(); 2188 final Thread reader = new Thread() { 2189 public void run() { 2190 try { stdout.read(); } 2191 catch (IOException e) { 2192 // Check that reader failed because stream was 2193 // asynchronously closed. 2194 // e.printStackTrace(); 2195 String msg = e.getMessage(); 2196 if (EnglishUnix.is() && 2197 ! (msg.matches(".*Bad file.*") || 2198 msg.matches(".*Stream closed.*"))) 2199 unexpected(e); 2200 if (matches(msg, SPAWNHELPER_FAILURE_MSG)) { 2201 unexpected(e); 2202 } 2203 } 2204 catch (Throwable t) { unexpected(t); }}}; 2205 reader.setDaemon(true); 2206 reader.start(); 2207 Thread.sleep(100); 2208 p.destroy(); 2209 check(p.waitFor() != 0); 2210 check(p.exitValue() != 0); 2211 // Subprocess is now dead, but file descriptors remain open. 2212 // Make sure the test will fail if we don't manage to close 2213 // the open streams within 30 seconds. Notice that this time 2214 // must be shorter than the sleep time of the grandchild. 2215 Timer t = new Timer("test/java/lang/ProcessBuilder/Basic.java process reaper", true); 2216 t.schedule(new TimerTask() { 2217 public void run() { 2218 fail("Subprocesses which create subprocesses of " + 2219 "their own caused the parent to hang while " + 2220 "waiting for file descriptors to be closed."); 2221 System.exit(-1); 2222 } 2223 }, 30000); 2224 stdout.close(); 2225 stderr.close(); 2226 stdin.close(); 2227 new ProcessBuilder(cmdkill).start(); 2228 // All streams successfully closed so we can cancel the timer. 2229 t.cancel(); 2230 //---------------------------------------------------------- 2231 // There remain unsolved issues with asynchronous close. 2232 // Here's a highly non-portable experiment to demonstrate: 2233 //---------------------------------------------------------- 2234 if (Boolean.getBoolean("wakeupJeff!")) { 2235 System.out.println("wakeupJeff!"); 2236 // Initialize signal handler for INTERRUPT_SIGNAL. 2237 new FileInputStream("/bin/sleep").getChannel().close(); 2238 // Send INTERRUPT_SIGNAL to every thread in this java. 2239 String[] wakeupJeff = { 2240 "/bin/bash", "-c", 2241 "/bin/ps --noheaders -Lfp $PPID | " + 2242 "/usr/bin/perl -nale 'print $F[3]' | " + 2243 // INTERRUPT_SIGNAL == 62 on my machine du jour. 2244 "/usr/bin/xargs kill -62" 2245 }; 2246 new ProcessBuilder(wakeupJeff).start().waitFor(); 2247 // If wakeupJeff worked, reader probably got EBADF. 2248 reader.join(); 2249 } 2250 } 2251 2252 //---------------------------------------------------------------- 2253 // Check the Process toString() method 2254 //---------------------------------------------------------------- 2255 { 2256 List<String> childArgs = new ArrayList<String>(javaChildArgs); 2257 childArgs.add("testIO"); 2258 ProcessBuilder pb = new ProcessBuilder(childArgs); 2259 pb.redirectInput(Redirect.PIPE); 2260 pb.redirectOutput(DISCARD); 2261 pb.redirectError(DISCARD); 2262 final Process p = pb.start(); 2263 // Child process waits until it gets input 2264 String s = p.toString(); 2265 check(s.contains("not exited")); 2266 check(s.contains("pid=" + p.pid() + ",")); 2267 2268 new PrintStream(p.getOutputStream()).print("standard input"); 2269 p.getOutputStream().close(); 2270 2271 // Check the toString after it exits 2272 int exitValue = p.waitFor(); 2273 s = p.toString(); 2274 check(s.contains("pid=" + p.pid() + ",")); 2275 check(s.contains("exitValue=" + exitValue) && 2276 !s.contains("not exited")); 2277 } 2278 } catch (Throwable t) { unexpected(t); } 2279 2280 //---------------------------------------------------------------- 2281 // Attempt to start process with insufficient permissions fails. 2282 //---------------------------------------------------------------- 2283 try { 2284 new File("emptyCommand").delete(); 2285 new FileOutputStream("emptyCommand").close(); 2286 new File("emptyCommand").setExecutable(false); 2287 new ProcessBuilder("./emptyCommand").start(); 2288 fail("Expected IOException not thrown"); 2289 } catch (IOException e) { 2290 new File("./emptyCommand").delete(); 2291 String m = e.getMessage(); 2292 if (EnglishUnix.is() && 2293 ! matches(m, PERMISSION_DENIED_ERROR_MSG)) 2294 unexpected(e); 2295 if (matches(m, SPAWNHELPER_FAILURE_MSG)) { 2296 unexpected(e); 2297 } 2298 } catch (Throwable t) { unexpected(t); } 2299 2300 new File("emptyCommand").delete(); 2301 2302 //---------------------------------------------------------------- 2303 // Check that Process.isAlive() & 2304 // Process.waitFor(0, TimeUnit.MILLISECONDS) work as expected. 2305 //---------------------------------------------------------------- 2306 try { 2307 List<String> childArgs = getSleepArgs(); 2308 final Process p = new ProcessBuilder(childArgs).start(); 2309 long start = System.nanoTime(); 2310 if (!p.isAlive() || p.waitFor(0, TimeUnit.MILLISECONDS)) { 2311 fail("Test failed: Process exited prematurely"); 2312 } 2313 long end = System.nanoTime(); 2314 // give waitFor(timeout) a wide berth (2s) 2315 System.out.printf(" waitFor process: delta: %d%n",(end - start) ); 2316 2317 if ((end - start) > TimeUnit.SECONDS.toNanos(2)) 2318 fail("Test failed: waitFor took too long (" + (end - start) + "ns)"); 2319 2320 p.destroy(); 2321 p.waitFor(); 2322 2323 if (p.isAlive() || 2324 !p.waitFor(0, TimeUnit.MILLISECONDS)) 2325 { 2326 fail("Test failed: Process still alive - please terminate " + 2327 p.toString() + " manually"); 2328 } 2329 } catch (Throwable t) { unexpected(t); } 2330 2331 //---------------------------------------------------------------- 2332 // Check that Process.waitFor(timeout, TimeUnit.MILLISECONDS) 2333 // works as expected. 2334 //---------------------------------------------------------------- 2335 try { 2336 List<String> childArgs = getSleepArgs(); 2337 final Process p = new ProcessBuilder(childArgs).start(); 2338 long start = System.nanoTime(); 2339 2340 if (p.waitFor(10, TimeUnit.MILLISECONDS)) { 2341 var msg = "External sleep process terminated early: exitValue: %d, (%dns)%n" 2342 .formatted(p.exitValue(), (System.nanoTime() - start)); 2343 fail(msg); 2344 } else { 2345 long end = System.nanoTime(); 2346 if ((end - start) < TimeUnit.MILLISECONDS.toNanos(10)) 2347 fail("Test failed: waitFor didn't take long enough (" + (end - start) + "ns)"); 2348 } 2349 p.destroy(); 2350 } catch (Throwable t) { unexpected(t); } 2351 2352 //---------------------------------------------------------------- 2353 // Check that Process.waitFor(timeout, TimeUnit.MILLISECONDS) 2354 // interrupt works as expected, if interrupted while waiting. 2355 //---------------------------------------------------------------- 2356 try { 2357 List<String> childArgs = getSleepArgs(); 2358 final Process p = new ProcessBuilder(childArgs).start(); 2359 final long start = System.nanoTime(); 2360 final CountDownLatch aboutToWaitFor = new CountDownLatch(1); 2361 2362 final Thread thread = new Thread() { 2363 public void run() { 2364 try { 2365 aboutToWaitFor.countDown(); 2366 Thread.currentThread().interrupt(); 2367 boolean result = p.waitFor(30L * 1000L, TimeUnit.MILLISECONDS); 2368 fail("waitFor() wasn't interrupted, its return value was: " + result); 2369 } catch (InterruptedException success) { 2370 } catch (Throwable t) { unexpected(t); } 2371 } 2372 }; 2373 2374 thread.start(); 2375 aboutToWaitFor.await(); 2376 thread.interrupt(); 2377 thread.join(10L * 1000L); 2378 check(millisElapsedSince(start) < 10L * 1000L); 2379 check(!thread.isAlive()); 2380 p.destroy(); 2381 } catch (Throwable t) { unexpected(t); } 2382 2383 //---------------------------------------------------------------- 2384 // Check that Process.waitFor(Long.MAX_VALUE, TimeUnit.MILLISECONDS) 2385 // interrupt works as expected, if interrupted while waiting. 2386 //---------------------------------------------------------------- 2387 try { 2388 List<String> childArgs = getSleepArgs(); 2389 final Process p = new ProcessBuilder(childArgs).start(); 2390 final long start = System.nanoTime(); 2391 final CountDownLatch aboutToWaitFor = new CountDownLatch(1); 2392 2393 final Thread thread = new Thread() { 2394 public void run() { 2395 try { 2396 aboutToWaitFor.countDown(); 2397 Thread.currentThread().interrupt(); 2398 boolean result = p.waitFor(Long.MAX_VALUE, TimeUnit.MILLISECONDS); 2399 fail("waitFor() wasn't interrupted, its return value was: " + result); 2400 } catch (InterruptedException success) { 2401 } catch (Throwable t) { unexpected(t); } 2402 } 2403 }; 2404 2405 thread.start(); 2406 aboutToWaitFor.await(); 2407 thread.interrupt(); 2408 thread.join(10L * 1000L); 2409 check(millisElapsedSince(start) < 10L * 1000L); 2410 check(!thread.isAlive()); 2411 p.destroy(); 2412 } catch (Throwable t) { unexpected(t); } 2413 2414 //---------------------------------------------------------------- 2415 // Check that Process.waitFor(timeout, TimeUnit.MILLISECONDS) 2416 // interrupt works as expected, if interrupted before waiting. 2417 //---------------------------------------------------------------- 2418 try { 2419 List<String> childArgs = getSleepArgs(); 2420 final Process p = new ProcessBuilder(childArgs).start(); 2421 final long start = System.nanoTime(); 2422 final CountDownLatch threadStarted = new CountDownLatch(1); 2423 2424 final Thread thread = new Thread() { 2425 public void run() { 2426 try { 2427 threadStarted.countDown(); 2428 do { Thread.yield(); } 2429 while (!Thread.currentThread().isInterrupted()); 2430 boolean result = p.waitFor(30L * 1000L, TimeUnit.MILLISECONDS); 2431 fail("waitFor() wasn't interrupted, its return value was: " + result); 2432 } catch (InterruptedException success) { 2433 } catch (Throwable t) { unexpected(t); } 2434 } 2435 }; 2436 2437 thread.start(); 2438 threadStarted.await(); 2439 thread.interrupt(); 2440 thread.join(10L * 1000L); 2441 check(millisElapsedSince(start) < 10L * 1000L); 2442 check(!thread.isAlive()); 2443 p.destroy(); 2444 } catch (Throwable t) { unexpected(t); } 2445 2446 //---------------------------------------------------------------- 2447 // Check that Process.waitFor(timeout, null) throws NPE. 2448 //---------------------------------------------------------------- 2449 try { 2450 List<String> childArgs = getSleepArgs(); 2451 final Process p = new ProcessBuilder(childArgs).start(); 2452 THROWS(NullPointerException.class, 2453 () -> p.waitFor(10L, null)); 2454 THROWS(NullPointerException.class, 2455 () -> p.waitFor(0L, null)); 2456 THROWS(NullPointerException.class, 2457 () -> p.waitFor(-1L, null)); 2458 // Terminate process and recheck after it exits 2459 p.destroy(); 2460 p.waitFor(); 2461 THROWS(NullPointerException.class, 2462 () -> p.waitFor(10L, null)); 2463 THROWS(NullPointerException.class, 2464 () -> p.waitFor(0L, null)); 2465 THROWS(NullPointerException.class, 2466 () -> p.waitFor(-1L, null)); 2467 } catch (Throwable t) { unexpected(t); } 2468 2469 //---------------------------------------------------------------- 2470 // Check that default implementation of Process.waitFor(timeout, null) throws NPE. 2471 //---------------------------------------------------------------- 2472 try { 2473 List<String> childArgs = getSleepArgs(); 2474 final Process proc = new ProcessBuilder(childArgs).start(); 2475 final DelegatingProcess p = new DelegatingProcess(proc); 2476 2477 THROWS(NullPointerException.class, 2478 () -> p.waitFor(10L, null)); 2479 THROWS(NullPointerException.class, 2480 () -> p.waitFor(0L, null)); 2481 THROWS(NullPointerException.class, 2482 () -> p.waitFor(-1L, null)); 2483 // Terminate process and recheck after it exits 2484 p.destroy(); 2485 p.waitFor(); 2486 THROWS(NullPointerException.class, 2487 () -> p.waitFor(10L, null)); 2488 THROWS(NullPointerException.class, 2489 () -> p.waitFor(0L, null)); 2490 THROWS(NullPointerException.class, 2491 () -> p.waitFor(-1L, null)); 2492 } catch (Throwable t) { unexpected(t); } 2493 2494 //---------------------------------------------------------------- 2495 // Check the default implementation for 2496 // Process.waitFor(long, TimeUnit) 2497 //---------------------------------------------------------------- 2498 try { 2499 List<String> childArgs = getSleepArgs(); 2500 final Process proc = new ProcessBuilder(childArgs).start(); 2501 DelegatingProcess p = new DelegatingProcess(proc); 2502 long start = System.nanoTime(); 2503 2504 if (p.waitFor(1000, TimeUnit.MILLISECONDS)) { 2505 var msg = "External sleep process terminated early: exitValue: %02x, (%dns)" 2506 .formatted(p.exitValue(), (System.nanoTime() - start)); 2507 fail(msg); 2508 } else { 2509 long end = System.nanoTime(); 2510 if ((end - start) < 500000000) 2511 fail("Test failed: waitFor didn't take long enough (" + (end - start) + "ns)"); 2512 } 2513 p.destroy(); 2514 2515 p.waitFor(1000, TimeUnit.MILLISECONDS); 2516 } catch (Throwable t) { unexpected(t); } 2517 } 2518 2519 // Path to native executables, if any 2520 private static final String TEST_NATIVEPATH = System.getProperty("test.nativepath"); 2521 2522 // Path where "sleep" program may be found" or null 2523 private static final Path SLEEP_PATH = initSleepPath(); 2524 2525 /** 2526 * Compute the Path to a sleep executable. 2527 * @return a Path to sleep or BasicSleep(.exe) or null if none 2528 */ 2529 private static Path initSleepPath() { 2530 if (Windows.is() && TEST_NATIVEPATH != null) { 2531 // exeBasicSleep is equivalent to sleep on Unix 2532 Path exePath = Path.of(TEST_NATIVEPATH).resolve("BasicSleep.exe"); 2533 if (Files.isExecutable(exePath)) { 2534 return exePath; 2535 } 2536 } 2537 2538 List<String> binPaths = List.of("/bin", "/usr/bin"); 2539 for (String dir : binPaths) { 2540 Path exePath = Path.of(dir).resolve("sleep"); 2541 if (Files.isExecutable(exePath)) { 2542 return exePath; 2543 } 2544 } 2545 return null; 2546 } 2547 2548 /** 2549 * Return the list of process arguments for a child to sleep 10 minutes (600 seconds). 2550 * 2551 * @return A list of process arguments to sleep 10 minutes. 2552 */ 2553 private static List<String> getSleepArgs() { 2554 List<String> childArgs = null; 2555 if (SLEEP_PATH != null) { 2556 childArgs = List.of(SLEEP_PATH.toString(), "600"); 2557 } else { 2558 // Fallback to the JavaChild ; its 'sleep' command is for 10 minutes. 2559 // The fallback the Java$Child is used if the test is run without building 2560 // the BasicSleep native executable (for Windows). 2561 childArgs = new ArrayList<>(javaChildArgs); 2562 childArgs.add("sleep"); 2563 System.out.println("Sleep not found, fallback to JavaChild: " + childArgs); 2564 } 2565 return childArgs; 2566 } 2567 2568 static void closeStreams(Process p) { 2569 try { 2570 p.getOutputStream().close(); 2571 p.getInputStream().close(); 2572 p.getErrorStream().close(); 2573 } catch (Throwable t) { unexpected(t); } 2574 } 2575 2576 private static class StreamAccumulator extends Thread { 2577 private final InputStream is; 2578 private final StringBuilder sb = new StringBuilder(); 2579 private Throwable throwable = null; 2580 2581 public String result () throws Throwable { 2582 if (throwable != null) 2583 throw throwable; 2584 return sb.toString(); 2585 } 2586 2587 StreamAccumulator (InputStream is) { 2588 this.is = is; 2589 } 2590 2591 public void run() { 2592 try { 2593 Reader r = new InputStreamReader(is); 2594 char[] buf = new char[4096]; 2595 int n; 2596 while ((n = r.read(buf)) > 0) { 2597 sb.append(buf,0,n); 2598 } 2599 } catch (Throwable t) { 2600 throwable = t; 2601 } finally { 2602 try { is.close(); } 2603 catch (Throwable t) { throwable = t; } 2604 } 2605 } 2606 } 2607 2608 static ProcessResults run(ProcessBuilder pb) { 2609 try { 2610 return run(pb.start()); 2611 } catch (Throwable t) { unexpected(t); return null; } 2612 } 2613 2614 private static ProcessResults run(Process p) { 2615 Throwable throwable = null; 2616 int exitValue = -1; 2617 String out = ""; 2618 String err = ""; 2619 2620 StreamAccumulator outAccumulator = 2621 new StreamAccumulator(p.getInputStream()); 2622 StreamAccumulator errAccumulator = 2623 new StreamAccumulator(p.getErrorStream()); 2624 2625 try { 2626 outAccumulator.start(); 2627 errAccumulator.start(); 2628 2629 exitValue = p.waitFor(); 2630 2631 outAccumulator.join(); 2632 errAccumulator.join(); 2633 2634 out = outAccumulator.result(); 2635 err = errAccumulator.result(); 2636 } catch (Throwable t) { 2637 throwable = t; 2638 } 2639 2640 return new ProcessResults(out, err, exitValue, throwable); 2641 } 2642 2643 //---------------------------------------------------------------- 2644 // Results of a command 2645 //---------------------------------------------------------------- 2646 private static class ProcessResults { 2647 private final String out; 2648 private final String err; 2649 private final int exitValue; 2650 private final Throwable throwable; 2651 2652 public ProcessResults(String out, 2653 String err, 2654 int exitValue, 2655 Throwable throwable) { 2656 this.out = out; 2657 this.err = err; 2658 this.exitValue = exitValue; 2659 this.throwable = throwable; 2660 } 2661 2662 public String out() { return out; } 2663 public String err() { return err; } 2664 public int exitValue() { return exitValue; } 2665 public Throwable throwable() { return throwable; } 2666 2667 public String toString() { 2668 StringBuilder sb = new StringBuilder(); 2669 sb.append("<STDOUT>\n" + out() + "</STDOUT>\n") 2670 .append("<STDERR>\n" + err() + "</STDERR>\n") 2671 .append("exitValue = " + exitValue + "\n"); 2672 if (throwable != null) 2673 sb.append(throwable.getStackTrace()); 2674 return sb.toString(); 2675 } 2676 } 2677 2678 //--------------------- Infrastructure --------------------------- 2679 static volatile int passed = 0, failed = 0; 2680 static void pass() {passed++;} 2681 static void fail() {failed++; Thread.dumpStack();} 2682 static void fail(String msg) {System.err.println(msg); fail();} 2683 static void unexpected(Throwable t) {failed++; t.printStackTrace();} 2684 static void check(boolean cond) {if (cond) pass(); else fail();} 2685 static void check(boolean cond, String m) {if (cond) pass(); else fail(m);} 2686 static void equal(Object x, Object y) { 2687 if (x == null ? y == null : x.equals(y)) pass(); 2688 else fail(">'" + x + "'<" + " not equal to " + "'" + y + "'");} 2689 2690 public static void main(String[] args) throws Throwable { 2691 try {realMain(args);} catch (Throwable t) {unexpected(t);} 2692 System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); 2693 if (failed > 0) throw new AssertionError("Some tests failed");} 2694 interface Fun {void f() throws Throwable;} 2695 static void THROWS(Class<? extends Throwable> k, Fun... fs) { 2696 for (Fun f : fs) 2697 try { f.f(); fail("Expected " + k.getName() + " not thrown"); } 2698 catch (Throwable t) { 2699 if (k.isAssignableFrom(t.getClass())) pass(); 2700 else unexpected(t);}} 2701 2702 static boolean isLocked(BufferedInputStream bis) throws Exception { 2703 return new Thread() { 2704 volatile boolean unlocked; 2705 2706 @Override 2707 public void run() { 2708 synchronized (bis) { unlocked = true; } 2709 } 2710 2711 boolean isLocked() throws InterruptedException { 2712 start(); 2713 join(10); 2714 return !unlocked; 2715 } 2716 }.isLocked(); 2717 } 2718 }