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 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 private static String matchAndExtract(String str, String regex) { 781 Matcher matcher = Pattern.compile(regex).matcher(str); 782 if (matcher.find()) { 783 return matcher.group(); 784 } else { 785 return ""; 786 } 787 } 788 789 /* Only used for Mac OS X -- 790 * Mac OS X (may) add the variable __CF_USER_TEXT_ENCODING to an empty 791 * environment. The environment variable JAVA_MAIN_CLASS_<pid> may also 792 * be set in Mac OS X. 793 * Remove them both from the list of env variables 794 */ 795 private static String removeMacExpectedVars(String vars) { 796 // Check for __CF_USER_TEXT_ENCODING 797 String cleanedVars = vars.replace("__CF_USER_TEXT_ENCODING=" 798 +cfUserTextEncoding+",",""); 799 // Check for JAVA_MAIN_CLASS_<pid> 800 String javaMainClassStr 801 = matchAndExtract(cleanedVars, 802 "JAVA_MAIN_CLASS_\\d+=Basic.JavaChild,"); 803 return cleanedVars.replace(javaMainClassStr,""); 804 } 805 806 /* Only used for AIX -- 807 * AIX adds the variable AIXTHREAD_GUARDPAGES=0 to the environment. 808 * Remove it from the list of env variables 809 */ 810 private static String removeAixExpectedVars(String vars) { 811 return vars.replace("AIXTHREAD_GUARDPAGES=0,", ""); 812 } 813 814 private static String sortByLinesWindowsly(String text) { 815 String[] lines = text.split("\n"); 816 Arrays.sort(lines, new WindowsComparator()); 817 StringBuilder sb = new StringBuilder(); 818 for (String line : lines) 819 sb.append(line).append("\n"); 820 return sb.toString(); 821 } 822 823 private static void checkMapSanity(Map<String,String> map) { 824 try { 825 Set<String> keySet = map.keySet(); 826 Collection<String> values = map.values(); 827 Set<Map.Entry<String,String>> entrySet = map.entrySet(); 828 829 equal(entrySet.size(), keySet.size()); 830 equal(entrySet.size(), values.size()); 831 832 StringBuilder s1 = new StringBuilder(); 833 for (Map.Entry<String,String> e : entrySet) 834 s1.append(e.getKey() + "=" + e.getValue() + "\n"); 835 836 StringBuilder s2 = new StringBuilder(); 837 for (String var : keySet) 838 s2.append(var + "=" + map.get(var) + "\n"); 839 840 equal(s1.toString(), s2.toString()); 841 842 Iterator<String> kIter = keySet.iterator(); 843 Iterator<String> vIter = values.iterator(); 844 Iterator<Map.Entry<String,String>> eIter = entrySet.iterator(); 845 846 while (eIter.hasNext()) { 847 Map.Entry<String,String> entry = eIter.next(); 848 String key = kIter.next(); 849 String value = vIter.next(); 850 check(entrySet.contains(entry)); 851 check(keySet.contains(key)); 852 check(values.contains(value)); 853 check(map.containsKey(key)); 854 check(map.containsValue(value)); 855 equal(entry.getKey(), key); 856 equal(entry.getValue(), value); 857 } 858 check(!kIter.hasNext() && 859 !vIter.hasNext()); 860 861 } catch (Throwable t) { unexpected(t); } 862 } 863 864 private static void checkMapEquality(Map<String,String> map1, 865 Map<String,String> map2) { 866 try { 867 equal(map1.size(), map2.size()); 868 equal(map1.isEmpty(), map2.isEmpty()); 869 for (String key : map1.keySet()) { 870 equal(map1.get(key), map2.get(key)); 871 check(map2.keySet().contains(key)); 872 } 873 equal(map1, map2); 874 equal(map2, map1); 875 equal(map1.entrySet(), map2.entrySet()); 876 equal(map2.entrySet(), map1.entrySet()); 877 equal(map1.keySet(), map2.keySet()); 878 equal(map2.keySet(), map1.keySet()); 879 880 equal(map1.hashCode(), map2.hashCode()); 881 equal(map1.entrySet().hashCode(), map2.entrySet().hashCode()); 882 equal(map1.keySet().hashCode(), map2.keySet().hashCode()); 883 } catch (Throwable t) { unexpected(t); } 884 } 885 886 static void checkRedirects(ProcessBuilder pb, 887 Redirect in, Redirect out, Redirect err) { 888 equal(pb.redirectInput(), in); 889 equal(pb.redirectOutput(), out); 890 equal(pb.redirectError(), err); 891 } 892 893 static void redirectIO(ProcessBuilder pb, 894 Redirect in, Redirect out, Redirect err) { 895 pb.redirectInput(in); 896 pb.redirectOutput(out); 897 pb.redirectError(err); 898 } 899 900 static void setFileContents(File file, String contents) { 901 try { 902 Writer w = new FileWriter(file); 903 w.write(contents); 904 w.close(); 905 } catch (Throwable t) { unexpected(t); } 906 } 907 908 static String fileContents(File file) { 909 try { 910 Reader r = new FileReader(file); 911 StringBuilder sb = new StringBuilder(); 912 char[] buffer = new char[1024]; 913 int n; 914 while ((n = r.read(buffer)) != -1) 915 sb.append(buffer,0,n); 916 r.close(); 917 return new String(sb); 918 } catch (Throwable t) { unexpected(t); return ""; } 919 } 920 921 @SuppressWarnings("removal") 922 static void testIORedirection() throws Throwable { 923 final File ifile = new File("ifile"); 924 final File ofile = new File("ofile"); 925 final File efile = new File("efile"); 926 ifile.delete(); 927 ofile.delete(); 928 efile.delete(); 929 930 //---------------------------------------------------------------- 931 // Check mutual inequality of different types of Redirect 932 //---------------------------------------------------------------- 933 Redirect[] redirects = 934 { PIPE, 935 INHERIT, 936 DISCARD, 937 Redirect.from(ifile), 938 Redirect.to(ifile), 939 Redirect.appendTo(ifile), 940 Redirect.from(ofile), 941 Redirect.to(ofile), 942 Redirect.appendTo(ofile), 943 }; 944 for (int i = 0; i < redirects.length; i++) 945 for (int j = 0; j < redirects.length; j++) 946 equal(redirects[i].equals(redirects[j]), (i == j)); 947 948 //---------------------------------------------------------------- 949 // Check basic properties of different types of Redirect 950 //---------------------------------------------------------------- 951 equal(PIPE.type(), Redirect.Type.PIPE); 952 equal(PIPE.toString(), "PIPE"); 953 equal(PIPE.file(), null); 954 955 equal(INHERIT.type(), Redirect.Type.INHERIT); 956 equal(INHERIT.toString(), "INHERIT"); 957 equal(INHERIT.file(), null); 958 959 equal(DISCARD.type(), Redirect.Type.WRITE); 960 equal(DISCARD.toString(), "WRITE"); 961 equal(DISCARD.file(), new File((Windows.is() ? "NUL" : "/dev/null"))); 962 963 equal(Redirect.from(ifile).type(), Redirect.Type.READ); 964 equal(Redirect.from(ifile).toString(), 965 "redirect to read from file \"ifile\""); 966 equal(Redirect.from(ifile).file(), ifile); 967 equal(Redirect.from(ifile), 968 Redirect.from(ifile)); 969 equal(Redirect.from(ifile).hashCode(), 970 Redirect.from(ifile).hashCode()); 971 972 equal(Redirect.to(ofile).type(), Redirect.Type.WRITE); 973 equal(Redirect.to(ofile).toString(), 974 "redirect to write to file \"ofile\""); 975 equal(Redirect.to(ofile).file(), ofile); 976 equal(Redirect.to(ofile), 977 Redirect.to(ofile)); 978 equal(Redirect.to(ofile).hashCode(), 979 Redirect.to(ofile).hashCode()); 980 981 equal(Redirect.appendTo(ofile).type(), Redirect.Type.APPEND); 982 equal(Redirect.appendTo(efile).toString(), 983 "redirect to append to file \"efile\""); 984 equal(Redirect.appendTo(efile).file(), efile); 985 equal(Redirect.appendTo(efile), 986 Redirect.appendTo(efile)); 987 equal(Redirect.appendTo(efile).hashCode(), 988 Redirect.appendTo(efile).hashCode()); 989 990 //---------------------------------------------------------------- 991 // Check initial values of redirects 992 //---------------------------------------------------------------- 993 List<String> childArgs = new ArrayList<String>(javaChildArgs); 994 childArgs.add("testIO"); 995 final ProcessBuilder pb = new ProcessBuilder(childArgs); 996 checkRedirects(pb, PIPE, PIPE, PIPE); 997 998 //---------------------------------------------------------------- 999 // Check inheritIO 1000 //---------------------------------------------------------------- 1001 pb.inheritIO(); 1002 checkRedirects(pb, INHERIT, INHERIT, INHERIT); 1003 1004 //---------------------------------------------------------------- 1005 // Check DISCARD for stdout,stderr 1006 //---------------------------------------------------------------- 1007 redirectIO(pb, INHERIT, DISCARD, DISCARD); 1008 checkRedirects(pb, INHERIT, DISCARD, DISCARD); 1009 1010 //---------------------------------------------------------------- 1011 // Check setters and getters agree 1012 //---------------------------------------------------------------- 1013 pb.redirectInput(ifile); 1014 equal(pb.redirectInput().file(), ifile); 1015 equal(pb.redirectInput(), Redirect.from(ifile)); 1016 1017 pb.redirectOutput(ofile); 1018 equal(pb.redirectOutput().file(), ofile); 1019 equal(pb.redirectOutput(), Redirect.to(ofile)); 1020 1021 pb.redirectError(efile); 1022 equal(pb.redirectError().file(), efile); 1023 equal(pb.redirectError(), Redirect.to(efile)); 1024 1025 THROWS(IllegalArgumentException.class, 1026 () -> pb.redirectInput(Redirect.to(ofile)), 1027 () -> pb.redirectOutput(Redirect.from(ifile)), 1028 () -> pb.redirectError(Redirect.from(ifile)), 1029 () -> pb.redirectInput(DISCARD)); 1030 1031 THROWS(NullPointerException.class, 1032 () -> pb.redirectInput((File)null), 1033 () -> pb.redirectOutput((File)null), 1034 () -> pb.redirectError((File)null), 1035 () -> pb.redirectInput((Redirect)null), 1036 () -> pb.redirectOutput((Redirect)null), 1037 () -> pb.redirectError((Redirect)null)); 1038 1039 THROWS(IOException.class, 1040 // Input file does not exist 1041 () -> pb.start()); 1042 setFileContents(ifile, "standard input"); 1043 1044 //---------------------------------------------------------------- 1045 // Writing to non-existent files 1046 //---------------------------------------------------------------- 1047 { 1048 ProcessResults r = run(pb); 1049 equal(r.exitValue(), 0); 1050 equal(fileContents(ofile), "standard output"); 1051 equal(fileContents(efile), "standard error"); 1052 equal(r.out(), ""); 1053 equal(r.err(), ""); 1054 ofile.delete(); 1055 efile.delete(); 1056 } 1057 1058 //---------------------------------------------------------------- 1059 // Both redirectErrorStream + redirectError 1060 //---------------------------------------------------------------- 1061 { 1062 pb.redirectErrorStream(true); 1063 ProcessResults r = run(pb); 1064 equal(r.exitValue(), 0); 1065 equal(fileContents(ofile), 1066 "standard error" + "standard output"); 1067 equal(fileContents(efile), ""); 1068 equal(r.out(), ""); 1069 equal(r.err(), ""); 1070 ofile.delete(); 1071 efile.delete(); 1072 } 1073 1074 //---------------------------------------------------------------- 1075 // Appending to existing files 1076 //---------------------------------------------------------------- 1077 { 1078 setFileContents(ofile, "ofile-contents"); 1079 setFileContents(efile, "efile-contents"); 1080 pb.redirectOutput(Redirect.appendTo(ofile)); 1081 pb.redirectError(Redirect.appendTo(efile)); 1082 pb.redirectErrorStream(false); 1083 ProcessResults r = run(pb); 1084 equal(r.exitValue(), 0); 1085 equal(fileContents(ofile), 1086 "ofile-contents" + "standard output"); 1087 equal(fileContents(efile), 1088 "efile-contents" + "standard error"); 1089 equal(r.out(), ""); 1090 equal(r.err(), ""); 1091 ofile.delete(); 1092 efile.delete(); 1093 } 1094 1095 //---------------------------------------------------------------- 1096 // Replacing existing files 1097 //---------------------------------------------------------------- 1098 { 1099 setFileContents(ofile, "ofile-contents"); 1100 setFileContents(efile, "efile-contents"); 1101 pb.redirectOutput(ofile); 1102 pb.redirectError(Redirect.to(efile)); 1103 ProcessResults r = run(pb); 1104 equal(r.exitValue(), 0); 1105 equal(fileContents(ofile), "standard output"); 1106 equal(fileContents(efile), "standard error"); 1107 equal(r.out(), ""); 1108 equal(r.err(), ""); 1109 ofile.delete(); 1110 efile.delete(); 1111 } 1112 1113 //---------------------------------------------------------------- 1114 // Appending twice to the same file? 1115 //---------------------------------------------------------------- 1116 { 1117 setFileContents(ofile, "ofile-contents"); 1118 setFileContents(efile, "efile-contents"); 1119 Redirect appender = Redirect.appendTo(ofile); 1120 pb.redirectOutput(appender); 1121 pb.redirectError(appender); 1122 ProcessResults r = run(pb); 1123 equal(r.exitValue(), 0); 1124 equal(fileContents(ofile), 1125 "ofile-contents" + 1126 "standard error" + 1127 "standard output"); 1128 equal(fileContents(efile), "efile-contents"); 1129 equal(r.out(), ""); 1130 equal(r.err(), ""); 1131 ifile.delete(); 1132 ofile.delete(); 1133 efile.delete(); 1134 } 1135 1136 //---------------------------------------------------------------- 1137 // DISCARDing output 1138 //---------------------------------------------------------------- 1139 { 1140 setFileContents(ifile, "standard input"); 1141 pb.redirectOutput(DISCARD); 1142 pb.redirectError(DISCARD); 1143 ProcessResults r = run(pb); 1144 equal(r.exitValue(), 0); 1145 equal(r.out(), ""); 1146 equal(r.err(), ""); 1147 } 1148 1149 //---------------------------------------------------------------- 1150 // DISCARDing output and redirecting error 1151 //---------------------------------------------------------------- 1152 { 1153 setFileContents(ifile, "standard input"); 1154 setFileContents(ofile, "ofile-contents"); 1155 setFileContents(efile, "efile-contents"); 1156 pb.redirectOutput(DISCARD); 1157 pb.redirectError(efile); 1158 ProcessResults r = run(pb); 1159 equal(r.exitValue(), 0); 1160 equal(fileContents(ofile), "ofile-contents"); 1161 equal(fileContents(efile), "standard error"); 1162 equal(r.out(), ""); 1163 equal(r.err(), ""); 1164 ofile.delete(); 1165 efile.delete(); 1166 } 1167 1168 //---------------------------------------------------------------- 1169 // DISCARDing error and redirecting output 1170 //---------------------------------------------------------------- 1171 { 1172 setFileContents(ifile, "standard input"); 1173 setFileContents(ofile, "ofile-contents"); 1174 setFileContents(efile, "efile-contents"); 1175 pb.redirectOutput(ofile); 1176 pb.redirectError(DISCARD); 1177 ProcessResults r = run(pb); 1178 equal(r.exitValue(), 0); 1179 equal(fileContents(ofile), "standard output"); 1180 equal(fileContents(efile), "efile-contents"); 1181 equal(r.out(), ""); 1182 equal(r.err(), ""); 1183 ofile.delete(); 1184 efile.delete(); 1185 } 1186 1187 //---------------------------------------------------------------- 1188 // DISCARDing output and merging error into output 1189 //---------------------------------------------------------------- 1190 { 1191 setFileContents(ifile, "standard input"); 1192 setFileContents(ofile, "ofile-contents"); 1193 setFileContents(efile, "efile-contents"); 1194 pb.redirectOutput(DISCARD); 1195 pb.redirectErrorStream(true); 1196 pb.redirectError(efile); 1197 ProcessResults r = run(pb); 1198 equal(r.exitValue(), 0); 1199 equal(fileContents(ofile), "ofile-contents"); // untouched 1200 equal(fileContents(efile), ""); // empty 1201 equal(r.out(), ""); 1202 equal(r.err(), ""); 1203 ifile.delete(); 1204 ofile.delete(); 1205 efile.delete(); 1206 pb.redirectErrorStream(false); // reset for next test 1207 } 1208 1209 //---------------------------------------------------------------- 1210 // Testing INHERIT is harder. 1211 // Note that this requires __FOUR__ nested JVMs involved in one test, 1212 // if you count the harness JVM. 1213 //---------------------------------------------------------------- 1214 for (String testName : new String[] { "testInheritIO", "testRedirectInherit" } ) { 1215 redirectIO(pb, PIPE, PIPE, PIPE); 1216 List<String> command = pb.command(); 1217 command.set(command.size() - 1, testName); 1218 Process p = pb.start(); 1219 new PrintStream(p.getOutputStream()).print("standard input"); 1220 p.getOutputStream().close(); 1221 ProcessResults r = run(p); 1222 equal(r.exitValue(), 0); 1223 equal(r.out(), "standard output"); 1224 equal(r.err(), "standard error"); 1225 } 1226 } 1227 1228 static void checkProcessPid() { 1229 ProcessBuilder pb = new ProcessBuilder(); 1230 List<String> list = new ArrayList<String>(javaChildArgs); 1231 list.add("pid"); 1232 pb.command(list); 1233 try { 1234 Process p = pb.start(); 1235 String s = commandOutput(p); 1236 long actualPid = Long.valueOf(s.trim()); 1237 long expectedPid = p.pid(); 1238 equal(actualPid, expectedPid); 1239 } catch (Throwable t) { 1240 unexpected(t); 1241 } 1242 1243 1244 // Test the default implementation of Process.getPid 1245 DelegatingProcess p = new DelegatingProcess(null); 1246 THROWS(UnsupportedOperationException.class, 1247 () -> p.pid(), 1248 () -> p.toHandle(), 1249 () -> p.supportsNormalTermination(), 1250 () -> p.children(), 1251 () -> p.descendants()); 1252 1253 } 1254 1255 @SuppressWarnings("removal") 1256 private static void realMain(String[] args) throws Throwable { 1257 if (Windows.is()) 1258 System.out.println("This appears to be a Windows system."); 1259 if (Unix.is()) 1260 System.out.println("This appears to be a Unix system."); 1261 if (UnicodeOS.is()) 1262 System.out.println("This appears to be a Unicode-based OS."); 1263 1264 try { testIORedirection(); } 1265 catch (Throwable t) { unexpected(t); } 1266 1267 //---------------------------------------------------------------- 1268 // Basic tests for getPid() 1269 //---------------------------------------------------------------- 1270 checkProcessPid(); 1271 1272 //---------------------------------------------------------------- 1273 // Basic tests for setting, replacing and deleting envvars 1274 //---------------------------------------------------------------- 1275 try { 1276 ProcessBuilder pb = new ProcessBuilder(); 1277 Map<String,String> environ = pb.environment(); 1278 1279 // New env var 1280 environ.put("QUUX", "BAR"); 1281 equal(environ.get("QUUX"), "BAR"); 1282 equal(getenvInChild(pb,"QUUX"), "BAR"); 1283 1284 // Modify env var 1285 environ.put("QUUX","bear"); 1286 equal(environ.get("QUUX"), "bear"); 1287 equal(getenvInChild(pb,"QUUX"), "bear"); 1288 checkMapSanity(environ); 1289 1290 // Remove env var 1291 environ.remove("QUUX"); 1292 equal(environ.get("QUUX"), null); 1293 equal(getenvInChild(pb,"QUUX"), "null"); 1294 checkMapSanity(environ); 1295 1296 // Remove non-existent env var 1297 environ.remove("QUUX"); 1298 equal(environ.get("QUUX"), null); 1299 equal(getenvInChild(pb,"QUUX"), "null"); 1300 checkMapSanity(environ); 1301 } catch (Throwable t) { unexpected(t); } 1302 1303 //---------------------------------------------------------------- 1304 // Pass Empty environment to child 1305 //---------------------------------------------------------------- 1306 try { 1307 ProcessBuilder pb = new ProcessBuilder(); 1308 pb.environment().clear(); 1309 String expected = Windows.is() ? "SystemRoot="+systemRoot+",": ""; 1310 expected = AIX.is() ? "LIBPATH="+libpath+",": expected; 1311 if (Windows.is()) { 1312 pb.environment().put("SystemRoot", systemRoot); 1313 } 1314 if (AIX.is()) { 1315 pb.environment().put("LIBPATH", libpath); 1316 } 1317 String result = getenvInChild(pb); 1318 if (MacOSX.is()) { 1319 result = removeMacExpectedVars(result); 1320 } 1321 if (AIX.is()) { 1322 result = removeAixExpectedVars(result); 1323 } 1324 equal(result, expected); 1325 } catch (Throwable t) { unexpected(t); } 1326 1327 //---------------------------------------------------------------- 1328 // System.getenv() is read-only. 1329 //---------------------------------------------------------------- 1330 THROWS(UnsupportedOperationException.class, 1331 () -> getenv().put("FOO","BAR"), 1332 () -> getenv().remove("PATH"), 1333 () -> getenv().keySet().remove("PATH"), 1334 () -> getenv().values().remove("someValue")); 1335 1336 try { 1337 Collection<Map.Entry<String,String>> c = getenv().entrySet(); 1338 if (! c.isEmpty()) 1339 try { 1340 c.iterator().next().setValue("foo"); 1341 fail("Expected UnsupportedOperationException not thrown"); 1342 } catch (UnsupportedOperationException e) {} // OK 1343 } catch (Throwable t) { unexpected(t); } 1344 1345 //---------------------------------------------------------------- 1346 // System.getenv() always returns the same object in our implementation. 1347 //---------------------------------------------------------------- 1348 try { 1349 check(System.getenv() == System.getenv()); 1350 } catch (Throwable t) { unexpected(t); } 1351 1352 //---------------------------------------------------------------- 1353 // You can't create an env var name containing "=", 1354 // or an env var name or value containing NUL. 1355 //---------------------------------------------------------------- 1356 { 1357 final Map<String,String> m = new ProcessBuilder().environment(); 1358 THROWS(IllegalArgumentException.class, 1359 () -> m.put("FOO=","BAR"), 1360 () -> m.put("FOO\u0000","BAR"), 1361 () -> m.put("FOO","BAR\u0000")); 1362 } 1363 1364 //---------------------------------------------------------------- 1365 // Commands must never be null. 1366 //---------------------------------------------------------------- 1367 THROWS(NullPointerException.class, 1368 () -> new ProcessBuilder((List<String>)null), 1369 () -> new ProcessBuilder().command((List<String>)null)); 1370 1371 //---------------------------------------------------------------- 1372 // Put in a command; get the same one back out. 1373 //---------------------------------------------------------------- 1374 try { 1375 List<String> command = new ArrayList<String>(); 1376 ProcessBuilder pb = new ProcessBuilder(command); 1377 check(pb.command() == command); 1378 List<String> command2 = new ArrayList<String>(2); 1379 command2.add("foo"); 1380 command2.add("bar"); 1381 pb.command(command2); 1382 check(pb.command() == command2); 1383 pb.command("foo", "bar"); 1384 check(pb.command() != command2 && pb.command().equals(command2)); 1385 pb.command(command2); 1386 command2.add("baz"); 1387 equal(pb.command().get(2), "baz"); 1388 } catch (Throwable t) { unexpected(t); } 1389 1390 //---------------------------------------------------------------- 1391 // Commands must contain at least one element. 1392 //---------------------------------------------------------------- 1393 THROWS(IndexOutOfBoundsException.class, 1394 () -> new ProcessBuilder().start(), 1395 () -> new ProcessBuilder(new ArrayList<String>()).start(), 1396 () -> Runtime.getRuntime().exec(new String[]{})); 1397 1398 //---------------------------------------------------------------- 1399 // Commands must not contain null elements at start() time. 1400 //---------------------------------------------------------------- 1401 THROWS(NullPointerException.class, 1402 () -> new ProcessBuilder("foo",null,"bar").start(), 1403 () -> new ProcessBuilder((String)null).start(), 1404 () -> new ProcessBuilder(new String[]{null}).start(), 1405 () -> new ProcessBuilder(new String[]{"foo",null,"bar"}).start()); 1406 1407 //---------------------------------------------------------------- 1408 // Command lists are growable. 1409 //---------------------------------------------------------------- 1410 try { 1411 new ProcessBuilder().command().add("foo"); 1412 new ProcessBuilder("bar").command().add("foo"); 1413 new ProcessBuilder(new String[]{"1","2"}).command().add("3"); 1414 } catch (Throwable t) { unexpected(t); } 1415 1416 //---------------------------------------------------------------- 1417 // Nulls in environment updates generate NullPointerException 1418 //---------------------------------------------------------------- 1419 try { 1420 final Map<String,String> env = new ProcessBuilder().environment(); 1421 THROWS(NullPointerException.class, 1422 () -> env.put("foo",null), 1423 () -> env.put(null,"foo"), 1424 () -> env.remove(null), 1425 () -> { for (Map.Entry<String,String> e : env.entrySet()) 1426 e.setValue(null);}, 1427 () -> Runtime.getRuntime().exec(new String[]{"foo"}, 1428 new String[]{null})); 1429 } catch (Throwable t) { unexpected(t); } 1430 1431 //---------------------------------------------------------------- 1432 // Non-String types in environment updates generate ClassCastException 1433 //---------------------------------------------------------------- 1434 try { 1435 final Map<String,String> env = new ProcessBuilder().environment(); 1436 THROWS(ClassCastException.class, 1437 () -> env.remove(TRUE), 1438 () -> env.keySet().remove(TRUE), 1439 () -> env.values().remove(TRUE), 1440 () -> env.entrySet().remove(TRUE)); 1441 } catch (Throwable t) { unexpected(t); } 1442 1443 //---------------------------------------------------------------- 1444 // Check query operations on environment maps 1445 //---------------------------------------------------------------- 1446 try { 1447 List<Map<String,String>> envs = 1448 new ArrayList<Map<String,String>>(2); 1449 envs.add(System.getenv()); 1450 envs.add(new ProcessBuilder().environment()); 1451 for (final Map<String,String> env : envs) { 1452 //---------------------------------------------------------------- 1453 // Nulls in environment queries are forbidden. 1454 //---------------------------------------------------------------- 1455 THROWS(NullPointerException.class, 1456 () -> getenv(null), 1457 () -> env.get(null), 1458 () -> env.containsKey(null), 1459 () -> env.containsValue(null), 1460 () -> env.keySet().contains(null), 1461 () -> env.values().contains(null)); 1462 1463 //---------------------------------------------------------------- 1464 // Non-String types in environment queries are forbidden. 1465 //---------------------------------------------------------------- 1466 THROWS(ClassCastException.class, 1467 () -> env.get(TRUE), 1468 () -> env.containsKey(TRUE), 1469 () -> env.containsValue(TRUE), 1470 () -> env.keySet().contains(TRUE), 1471 () -> env.values().contains(TRUE)); 1472 1473 //---------------------------------------------------------------- 1474 // Illegal String values in environment queries are (grumble) OK 1475 //---------------------------------------------------------------- 1476 equal(env.get("\u0000"), null); 1477 check(! env.containsKey("\u0000")); 1478 check(! env.containsValue("\u0000")); 1479 check(! env.keySet().contains("\u0000")); 1480 check(! env.values().contains("\u0000")); 1481 } 1482 1483 } catch (Throwable t) { unexpected(t); } 1484 1485 try { 1486 final Set<Map.Entry<String,String>> entrySet = 1487 new ProcessBuilder().environment().entrySet(); 1488 THROWS(NullPointerException.class, 1489 () -> entrySet.contains(null)); 1490 THROWS(ClassCastException.class, 1491 () -> entrySet.contains(TRUE), 1492 () -> entrySet.contains( 1493 new SimpleImmutableEntry<Boolean,String>(TRUE,""))); 1494 1495 check(! entrySet.contains 1496 (new SimpleImmutableEntry<String,String>("", ""))); 1497 } catch (Throwable t) { unexpected(t); } 1498 1499 //---------------------------------------------------------------- 1500 // Put in a directory; get the same one back out. 1501 //---------------------------------------------------------------- 1502 try { 1503 ProcessBuilder pb = new ProcessBuilder(); 1504 File foo = new File("foo"); 1505 equal(pb.directory(), null); 1506 equal(pb.directory(foo).directory(), foo); 1507 equal(pb.directory(null).directory(), null); 1508 } catch (Throwable t) { unexpected(t); } 1509 1510 //---------------------------------------------------------------- 1511 // If round-trip conversion works, check envvar pass-through to child 1512 //---------------------------------------------------------------- 1513 try { 1514 testEncoding("ASCII", "xyzzy"); 1515 testEncoding("Latin1", "\u00f1\u00e1"); 1516 testEncoding("Unicode", "\u22f1\u11e1"); 1517 } catch (Throwable t) { unexpected(t); } 1518 1519 //---------------------------------------------------------------- 1520 // A surprisingly large number of ways to delete an environment var. 1521 //---------------------------------------------------------------- 1522 testVariableDeleter(new EnvironmentFrobber() { 1523 public void doIt(Map<String,String> environ) { 1524 environ.remove("Foo");}}); 1525 1526 testVariableDeleter(new EnvironmentFrobber() { 1527 public void doIt(Map<String,String> environ) { 1528 environ.keySet().remove("Foo");}}); 1529 1530 testVariableDeleter(new EnvironmentFrobber() { 1531 public void doIt(Map<String,String> environ) { 1532 environ.values().remove("BAAR");}}); 1533 1534 testVariableDeleter(new EnvironmentFrobber() { 1535 public void doIt(Map<String,String> environ) { 1536 // Legally fabricate a ProcessEnvironment.StringEntry, 1537 // even though it's private. 1538 Map<String,String> environ2 1539 = new ProcessBuilder().environment(); 1540 environ2.clear(); 1541 environ2.put("Foo","BAAR"); 1542 // Subtlety alert. 1543 Map.Entry<String,String> e 1544 = environ2.entrySet().iterator().next(); 1545 environ.entrySet().remove(e);}}); 1546 1547 testVariableDeleter(new EnvironmentFrobber() { 1548 public void doIt(Map<String,String> environ) { 1549 Map.Entry<String,String> victim = null; 1550 for (Map.Entry<String,String> e : environ.entrySet()) 1551 if (e.getKey().equals("Foo")) 1552 victim = e; 1553 if (victim != null) 1554 environ.entrySet().remove(victim);}}); 1555 1556 testVariableDeleter(new EnvironmentFrobber() { 1557 public void doIt(Map<String,String> environ) { 1558 Iterator<String> it = environ.keySet().iterator(); 1559 while (it.hasNext()) { 1560 String val = it.next(); 1561 if (val.equals("Foo")) 1562 it.remove();}}}); 1563 1564 testVariableDeleter(new EnvironmentFrobber() { 1565 public void doIt(Map<String,String> environ) { 1566 Iterator<Map.Entry<String,String>> it 1567 = environ.entrySet().iterator(); 1568 while (it.hasNext()) { 1569 Map.Entry<String,String> e = it.next(); 1570 if (e.getKey().equals("Foo")) 1571 it.remove();}}}); 1572 1573 testVariableDeleter(new EnvironmentFrobber() { 1574 public void doIt(Map<String,String> environ) { 1575 Iterator<String> it = environ.values().iterator(); 1576 while (it.hasNext()) { 1577 String val = it.next(); 1578 if (val.equals("BAAR")) 1579 it.remove();}}}); 1580 1581 //---------------------------------------------------------------- 1582 // A surprisingly small number of ways to add an environment var. 1583 //---------------------------------------------------------------- 1584 testVariableAdder(new EnvironmentFrobber() { 1585 public void doIt(Map<String,String> environ) { 1586 environ.put("Foo","Bahrein");}}); 1587 1588 //---------------------------------------------------------------- 1589 // A few ways to modify an environment var. 1590 //---------------------------------------------------------------- 1591 testVariableModifier(new EnvironmentFrobber() { 1592 public void doIt(Map<String,String> environ) { 1593 environ.put("Foo","NewValue");}}); 1594 1595 testVariableModifier(new EnvironmentFrobber() { 1596 public void doIt(Map<String,String> environ) { 1597 for (Map.Entry<String,String> e : environ.entrySet()) 1598 if (e.getKey().equals("Foo")) 1599 e.setValue("NewValue");}}); 1600 1601 //---------------------------------------------------------------- 1602 // Fiddle with environment sizes 1603 //---------------------------------------------------------------- 1604 try { 1605 Map<String,String> environ = new ProcessBuilder().environment(); 1606 int size = environ.size(); 1607 checkSizes(environ, size); 1608 1609 environ.put("UnLiKeLYeNVIROmtNam", "someVal"); 1610 checkSizes(environ, size+1); 1611 1612 // Check for environment independence 1613 new ProcessBuilder().environment().clear(); 1614 1615 environ.put("UnLiKeLYeNVIROmtNam", "someOtherVal"); 1616 checkSizes(environ, size+1); 1617 1618 environ.remove("UnLiKeLYeNVIROmtNam"); 1619 checkSizes(environ, size); 1620 1621 environ.clear(); 1622 checkSizes(environ, 0); 1623 1624 environ.clear(); 1625 checkSizes(environ, 0); 1626 1627 environ = new ProcessBuilder().environment(); 1628 environ.keySet().clear(); 1629 checkSizes(environ, 0); 1630 1631 environ = new ProcessBuilder().environment(); 1632 environ.entrySet().clear(); 1633 checkSizes(environ, 0); 1634 1635 environ = new ProcessBuilder().environment(); 1636 environ.values().clear(); 1637 checkSizes(environ, 0); 1638 } catch (Throwable t) { unexpected(t); } 1639 1640 //---------------------------------------------------------------- 1641 // Check that various map invariants hold 1642 //---------------------------------------------------------------- 1643 checkMapSanity(new ProcessBuilder().environment()); 1644 checkMapSanity(System.getenv()); 1645 checkMapEquality(new ProcessBuilder().environment(), 1646 new ProcessBuilder().environment()); 1647 1648 1649 //---------------------------------------------------------------- 1650 // Check effects on external "env" command. 1651 //---------------------------------------------------------------- 1652 try { 1653 Set<String> env1 = new HashSet<String> 1654 (Arrays.asList(nativeEnv((String[])null).split("\n"))); 1655 1656 ProcessBuilder pb = new ProcessBuilder(); 1657 pb.environment().put("QwErTyUiOp","AsDfGhJk"); 1658 1659 Set<String> env2 = new HashSet<String> 1660 (Arrays.asList(nativeEnv(pb).split("\n"))); 1661 1662 check(env2.size() == env1.size() + 1); 1663 env1.add("QwErTyUiOp=AsDfGhJk"); 1664 check(env1.equals(env2)); 1665 } catch (Throwable t) { unexpected(t); } 1666 1667 //---------------------------------------------------------------- 1668 // Test Runtime.exec(...envp...) 1669 // Check for sort order of environment variables on Windows. 1670 //---------------------------------------------------------------- 1671 try { 1672 String systemRoot = "SystemRoot=" + System.getenv("SystemRoot"); 1673 // '+' < 'A' < 'Z' < '_' < 'a' < 'z' < '~' 1674 String[]envp = {"FOO=BAR","BAZ=GORP","QUUX=", 1675 "+=+", "_=_", "~=~", systemRoot}; 1676 String output = nativeEnv(envp); 1677 String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\n"+systemRoot+"\n_=_\n~=~\n"; 1678 // On Windows, Java must keep the environment sorted. 1679 // Order is random on Unix, so this test does the sort. 1680 if (! Windows.is()) 1681 output = sortByLinesWindowsly(output); 1682 equal(output, expected); 1683 } catch (Throwable t) { unexpected(t); } 1684 1685 //---------------------------------------------------------------- 1686 // Test Runtime.exec(...envp...) 1687 // and check SystemRoot gets set automatically on Windows 1688 //---------------------------------------------------------------- 1689 try { 1690 if (Windows.is()) { 1691 String systemRoot = "SystemRoot=" + System.getenv("SystemRoot"); 1692 String[]envp = {"FOO=BAR","BAZ=GORP","QUUX=", 1693 "+=+", "_=_", "~=~"}; 1694 String output = nativeEnv(envp); 1695 String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\n"+systemRoot+"\n_=_\n~=~\n"; 1696 equal(output, expected); 1697 } 1698 } catch (Throwable t) { unexpected(t); } 1699 1700 //---------------------------------------------------------------- 1701 // System.getenv() must be consistent with System.getenv(String) 1702 //---------------------------------------------------------------- 1703 try { 1704 for (Map.Entry<String,String> e : getenv().entrySet()) 1705 equal(getenv(e.getKey()), e.getValue()); 1706 } catch (Throwable t) { unexpected(t); } 1707 1708 //---------------------------------------------------------------- 1709 // Fiddle with working directory in child 1710 //---------------------------------------------------------------- 1711 try { 1712 String canonicalUserDir = 1713 new File(System.getProperty("user.dir")).getCanonicalPath(); 1714 String[] sdirs = new String[] 1715 {".", "..", "/", "/bin", 1716 "C:", "c:", "C:/", "c:\\", "\\", "\\bin", 1717 "c:\\windows ", "c:\\Program Files", "c:\\Program Files\\" }; 1718 for (String sdir : sdirs) { 1719 File dir = new File(sdir); 1720 if (! (dir.isDirectory() && dir.exists())) 1721 continue; 1722 out.println("Testing directory " + dir); 1723 //dir = new File(dir.getCanonicalPath()); 1724 1725 ProcessBuilder pb = new ProcessBuilder(); 1726 equal(pb.directory(), null); 1727 equal(pwdInChild(pb), canonicalUserDir); 1728 1729 pb.directory(dir); 1730 equal(pb.directory(), dir); 1731 equal(pwdInChild(pb), dir.getCanonicalPath()); 1732 1733 pb.directory(null); 1734 equal(pb.directory(), null); 1735 equal(pwdInChild(pb), canonicalUserDir); 1736 1737 pb.directory(dir); 1738 } 1739 } catch (Throwable t) { unexpected(t); } 1740 1741 //---------------------------------------------------------------- 1742 // Working directory with Unicode in child 1743 //---------------------------------------------------------------- 1744 try { 1745 if (UnicodeOS.is()) { 1746 File dir = new File(System.getProperty("test.dir", "."), 1747 "ProcessBuilderDir\u4e00\u4e02"); 1748 try { 1749 if (!dir.exists()) 1750 dir.mkdir(); 1751 out.println("Testing Unicode directory:" + dir); 1752 ProcessBuilder pb = new ProcessBuilder(); 1753 pb.directory(dir); 1754 equal(pwdInChild(pb), dir.getCanonicalPath()); 1755 } finally { 1756 if (dir.exists()) 1757 dir.delete(); 1758 } 1759 } 1760 } catch (Throwable t) { unexpected(t); } 1761 1762 //---------------------------------------------------------------- 1763 // OOME in child allocating maximally sized array 1764 // Test for hotspot/jvmti bug 6850957 1765 //---------------------------------------------------------------- 1766 try { 1767 List<String> list = new ArrayList<String>(javaChildArgs); 1768 list.add(1, String.format("-XX:OnOutOfMemoryError=%s -version", 1769 javaExe)); 1770 list.add("ArrayOOME"); 1771 ProcessResults r = run(new ProcessBuilder(list)); 1772 check(r.err().contains("java.lang.OutOfMemoryError:")); 1773 check(r.err().contains(javaExe)); 1774 check(r.err().contains(System.getProperty("java.version"))); 1775 equal(r.exitValue(), 1); 1776 } catch (Throwable t) { unexpected(t); } 1777 1778 //---------------------------------------------------------------- 1779 // Windows has tricky semi-case-insensitive semantics 1780 //---------------------------------------------------------------- 1781 if (Windows.is()) 1782 try { 1783 out.println("Running case insensitve variable tests"); 1784 for (String[] namePair : 1785 new String[][] 1786 { new String[]{"PATH","PaTh"}, 1787 new String[]{"home","HOME"}, 1788 new String[]{"SYSTEMROOT","SystemRoot"}}) { 1789 check((getenv(namePair[0]) == null && 1790 getenv(namePair[1]) == null) 1791 || 1792 getenv(namePair[0]).equals(getenv(namePair[1])), 1793 "Windows environment variables are not case insensitive"); 1794 } 1795 } catch (Throwable t) { unexpected(t); } 1796 1797 //---------------------------------------------------------------- 1798 // Test proper Unicode child environment transfer 1799 //---------------------------------------------------------------- 1800 if (UnicodeOS.is()) 1801 try { 1802 ProcessBuilder pb = new ProcessBuilder(); 1803 pb.environment().put("\u1234","\u5678"); 1804 pb.environment().remove("PATH"); 1805 equal(getenvInChild1234(pb), "\u5678"); 1806 } catch (Throwable t) { unexpected(t); } 1807 1808 1809 //---------------------------------------------------------------- 1810 // Test Runtime.exec(...envp...) with envstrings with initial `=' 1811 //---------------------------------------------------------------- 1812 try { 1813 List<String> childArgs = new ArrayList<String>(javaChildArgs); 1814 childArgs.add("System.getenv()"); 1815 String[] cmdp = childArgs.toArray(new String[childArgs.size()]); 1816 String[] envp; 1817 String[] envpWin = {"=C:=\\", "=ExitValue=3", "SystemRoot="+systemRoot}; 1818 String[] envpOth = {"=ExitValue=3", "=C:=\\"}; 1819 if (Windows.is()) { 1820 envp = envpWin; 1821 } else if (AIX.is()) { 1822 envp = new String[] {"=ExitValue=3", "=C:=\\", "LIBPATH=" + libpath}; 1823 } else { 1824 envp = envpOth; 1825 } 1826 Process p = Runtime.getRuntime().exec(cmdp, envp); 1827 String expected = Windows.is() ? "=C:=\\,=ExitValue=3,SystemRoot="+systemRoot+"," : "=C:=\\,"; 1828 expected = AIX.is() ? expected + "LIBPATH="+libpath+",": expected; 1829 String commandOutput = commandOutput(p); 1830 if (MacOSX.is()) { 1831 commandOutput = removeMacExpectedVars(commandOutput); 1832 } 1833 if (AIX.is()) { 1834 commandOutput = removeAixExpectedVars(commandOutput); 1835 } 1836 equal(commandOutput, expected); 1837 if (Windows.is()) { 1838 ProcessBuilder pb = new ProcessBuilder(childArgs); 1839 pb.environment().clear(); 1840 pb.environment().put("SystemRoot", systemRoot); 1841 pb.environment().put("=ExitValue", "3"); 1842 pb.environment().put("=C:", "\\"); 1843 equal(commandOutput(pb), expected); 1844 } 1845 } catch (Throwable t) { unexpected(t); } 1846 1847 //---------------------------------------------------------------- 1848 // Test Runtime.exec(...envp...) with envstrings without any `=' 1849 //---------------------------------------------------------------- 1850 try { 1851 // In Windows CMD (`cmd.exe`), `echo/` outputs a newline (i.e., an empty line). 1852 // Wrapping it with `cmd.exe /c` ensures compatibility in both native Windows and Cygwin environments. 1853 String[] cmdp = Windows.is() ? new String[]{"cmd.exe", "/c", "echo/"} : new String[]{"echo"}; 1854 String[] envp = {"Hello", "World"}; // Yuck! 1855 Process p = Runtime.getRuntime().exec(cmdp, envp); 1856 equal(commandOutput(p), "\n"); 1857 } catch (Throwable t) { unexpected(t); } 1858 1859 //---------------------------------------------------------------- 1860 // Test Runtime.exec(...envp...) with envstrings containing NULs 1861 //---------------------------------------------------------------- 1862 try { 1863 List<String> childArgs = new ArrayList<String>(javaChildArgs); 1864 childArgs.add("System.getenv()"); 1865 String[] cmdp = childArgs.toArray(new String[childArgs.size()]); 1866 String[] envpWin = {"SystemRoot="+systemRoot, "LC_ALL=C\u0000\u0000", // Yuck! 1867 "FO\u0000=B\u0000R"}; 1868 String[] envpOth = {"LC_ALL=C\u0000\u0000", // Yuck! 1869 "FO\u0000=B\u0000R"}; 1870 String[] envp; 1871 if (Windows.is()) { 1872 envp = envpWin; 1873 } else if (AIX.is()) { 1874 envp = new String[] {"LC_ALL=C\u0000\u0000", // Yuck! 1875 "FO\u0000=B\u0000R", "LIBPATH=" + libpath}; 1876 } else { 1877 envp = envpOth; 1878 } 1879 System.out.println ("cmdp"); 1880 for (int i=0; i<cmdp.length; i++) { 1881 System.out.printf ("cmdp %d: %s\n", i, cmdp[i]); 1882 } 1883 System.out.println ("envp"); 1884 for (int i=0; i<envp.length; i++) { 1885 System.out.printf ("envp %d: %s\n", i, envp[i]); 1886 } 1887 Process p = Runtime.getRuntime().exec(cmdp, envp); 1888 String commandOutput = commandOutput(p); 1889 if (MacOSX.is()) { 1890 commandOutput = removeMacExpectedVars(commandOutput); 1891 } 1892 if (AIX.is()) { 1893 commandOutput = removeAixExpectedVars(commandOutput); 1894 } 1895 check(commandOutput.equals(Windows.is() 1896 ? "LC_ALL=C,SystemRoot="+systemRoot+"," 1897 : AIX.is() 1898 ? "LC_ALL=C,LIBPATH="+libpath+"," 1899 : "LC_ALL=C,"), 1900 "Incorrect handling of envstrings containing NULs"); 1901 } catch (Throwable t) { unexpected(t); } 1902 1903 //---------------------------------------------------------------- 1904 // Test the redirectErrorStream property 1905 //---------------------------------------------------------------- 1906 try { 1907 ProcessBuilder pb = new ProcessBuilder(); 1908 equal(pb.redirectErrorStream(), false); 1909 equal(pb.redirectErrorStream(true), pb); 1910 equal(pb.redirectErrorStream(), true); 1911 equal(pb.redirectErrorStream(false), pb); 1912 equal(pb.redirectErrorStream(), false); 1913 } catch (Throwable t) { unexpected(t); } 1914 1915 try { 1916 List<String> childArgs = new ArrayList<String>(javaChildArgs); 1917 childArgs.add("OutErr"); 1918 ProcessBuilder pb = new ProcessBuilder(childArgs); 1919 { 1920 ProcessResults r = run(pb); 1921 equal(r.out(), "outout"); 1922 equal(r.err(), "errerr"); 1923 } 1924 { 1925 pb.redirectErrorStream(true); 1926 ProcessResults r = run(pb); 1927 equal(r.out(), "outerrouterr"); 1928 equal(r.err(), ""); 1929 } 1930 } catch (Throwable t) { unexpected(t); } 1931 1932 if (Unix.is()) { 1933 //---------------------------------------------------------------- 1934 // We can find true and false when PATH is null 1935 //---------------------------------------------------------------- 1936 try { 1937 List<String> childArgs = new ArrayList<String>(javaChildArgs); 1938 childArgs.add("null PATH"); 1939 ProcessBuilder pb = new ProcessBuilder(childArgs); 1940 pb.environment().remove("PATH"); 1941 ProcessResults r = run(pb); 1942 equal(r.out(), ""); 1943 equal(r.err(), ""); 1944 equal(r.exitValue(), 0); 1945 } catch (Throwable t) { unexpected(t); } 1946 1947 //---------------------------------------------------------------- 1948 // PATH search algorithm on Unix 1949 //---------------------------------------------------------------- 1950 try { 1951 List<String> childArgs = new ArrayList<String>(javaChildArgs); 1952 childArgs.add("PATH search algorithm"); 1953 ProcessBuilder pb = new ProcessBuilder(childArgs); 1954 pb.environment().put("PATH", "dir1:dir2:"); 1955 ProcessResults r = run(pb); 1956 equal(r.out(), ""); 1957 equal(r.err(), ""); 1958 equal(r.exitValue(), True.exitValue()); 1959 } catch (Throwable t) { unexpected(t); } 1960 1961 //---------------------------------------------------------------- 1962 // Parent's, not child's PATH is used 1963 //---------------------------------------------------------------- 1964 try { 1965 new File("suBdiR").mkdirs(); 1966 copy(TrueExe.path(), "suBdiR/unliKely"); 1967 final ProcessBuilder pb = 1968 new ProcessBuilder(new String[]{"unliKely"}); 1969 pb.environment().put("PATH", "suBdiR"); 1970 THROWS(IOException.class, () -> pb.start()); 1971 } catch (Throwable t) { unexpected(t); 1972 } finally { 1973 new File("suBdiR/unliKely").delete(); 1974 new File("suBdiR").delete(); 1975 } 1976 } 1977 1978 //---------------------------------------------------------------- 1979 // Attempt to start bogus program "" 1980 //---------------------------------------------------------------- 1981 try { 1982 new ProcessBuilder("").start(); 1983 fail("Expected IOException not thrown"); 1984 } catch (IOException e) { 1985 String m = e.getMessage(); 1986 if (EnglishUnix.is() && 1987 ! matches(m, NO_SUCH_FILE_ERROR_MSG)) 1988 unexpected(e); 1989 if (matches(m, SPAWNHELPER_FAILURE_MSG)) 1990 unexpected(e); 1991 } catch (Throwable t) { unexpected(t); } 1992 1993 //---------------------------------------------------------------- 1994 // Check that attempt to execute program name with funny 1995 // characters throws an exception containing those characters. 1996 //---------------------------------------------------------------- 1997 for (String programName : new String[] {"\u00f0", "\u01f0"}) 1998 try { 1999 new ProcessBuilder(programName).start(); 2000 fail("Expected IOException not thrown"); 2001 } catch (IOException e) { 2002 String m = e.getMessage(); 2003 Pattern p = Pattern.compile(programName); 2004 if (! matches(m, programName) 2005 || (EnglishUnix.is() && 2006 ! matches(m, NO_SUCH_FILE_ERROR_MSG))) 2007 unexpected(e); 2008 if (matches(m, SPAWNHELPER_FAILURE_MSG)) 2009 unexpected(e); 2010 } catch (Throwable t) { unexpected(t); } 2011 2012 //---------------------------------------------------------------- 2013 // Attempt to start process in nonexistent directory fails. 2014 //---------------------------------------------------------------- 2015 try { 2016 new ProcessBuilder("echo") 2017 .directory(new File("UnLiKeLY")) 2018 .start(); 2019 fail("Expected IOException not thrown"); 2020 } catch (IOException e) { 2021 String m = e.getMessage(); 2022 if (! matches(m, "in directory") 2023 || (EnglishUnix.is() && 2024 ! matches(m, NO_SUCH_FILE_ERROR_MSG))) 2025 unexpected(e); 2026 if (matches(m, SPAWNHELPER_FAILURE_MSG)) 2027 unexpected(e); 2028 } catch (Throwable t) { unexpected(t); } 2029 2030 //---------------------------------------------------------------- 2031 // Attempt to write 4095 bytes to the pipe buffer without a 2032 // reader to drain it would deadlock, if not for the fact that 2033 // interprocess pipe buffers are at least 4096 bytes. 2034 // 2035 // Also, check that available reports all the bytes expected 2036 // in the pipe buffer, and that I/O operations do the expected 2037 // things. 2038 //---------------------------------------------------------------- 2039 try { 2040 List<String> childArgs = new ArrayList<String>(javaChildArgs); 2041 childArgs.add("print4095"); 2042 final int SIZE = 4095; 2043 final Process p = new ProcessBuilder(childArgs).start(); 2044 print4095(p.getOutputStream(), (byte) '!'); // Might hang! 2045 p.waitFor(); // Might hang! 2046 equal(SIZE, p.getInputStream().available()); 2047 equal(SIZE, p.getErrorStream().available()); 2048 THROWS(IOException.class, 2049 () -> { p.getOutputStream().write((byte) '!'); 2050 p.getOutputStream().flush();}); 2051 2052 final byte[] bytes = new byte[SIZE + 1]; 2053 equal(SIZE, p.getInputStream().read(bytes)); 2054 for (int i = 0; i < SIZE; i++) 2055 equal((byte) '!', bytes[i]); 2056 equal((byte) 0, bytes[SIZE]); 2057 2058 equal(SIZE, p.getErrorStream().read(bytes)); 2059 for (int i = 0; i < SIZE; i++) 2060 equal((byte) 'E', bytes[i]); 2061 equal((byte) 0, bytes[SIZE]); 2062 2063 equal(0, p.getInputStream().available()); 2064 equal(0, p.getErrorStream().available()); 2065 equal(-1, p.getErrorStream().read()); 2066 equal(-1, p.getInputStream().read()); 2067 2068 equal(p.exitValue(), 5); 2069 2070 p.getInputStream().close(); 2071 p.getErrorStream().close(); 2072 try { p.getOutputStream().close(); } catch (IOException flushFailed) { } 2073 2074 InputStream[] streams = { p.getInputStream(), p.getErrorStream() }; 2075 for (final InputStream in : streams) { 2076 Fun[] ops = { 2077 () -> in.read(), 2078 () -> in.read(bytes), 2079 () -> in.available() 2080 }; 2081 for (Fun op : ops) { 2082 try { 2083 op.f(); 2084 fail(); 2085 } catch (IOException expected) { 2086 String m = expected.getMessage(); 2087 check(m.matches("[Ss]tream [Cc]losed")); 2088 check(!matches(m, SPAWNHELPER_FAILURE_MSG)); 2089 } 2090 } 2091 } 2092 } catch (Throwable t) { unexpected(t); } 2093 2094 //---------------------------------------------------------------- 2095 // Check that reads which are pending when Process.destroy is 2096 // called, get EOF, or IOException("Stream closed"). 2097 //---------------------------------------------------------------- 2098 try { 2099 final int cases = 4; 2100 for (int i = 0; i < cases; i++) { 2101 final int action = i; 2102 List<String> childArgs = getSleepArgs(); 2103 final ProcessBuilder pb = new ProcessBuilder(childArgs); 2104 final byte[] bytes = new byte[10]; 2105 final Process p = pb.start(); 2106 final CountDownLatch latch = new CountDownLatch(1); 2107 final InputStream s; 2108 switch (action & 0x1) { 2109 case 0: s = p.getInputStream(); break; 2110 case 1: s = p.getErrorStream(); break; 2111 default: throw new Error(); 2112 } 2113 final Thread thread = new Thread() { 2114 public void run() { 2115 try { 2116 int r; 2117 latch.countDown(); 2118 switch (action & 0x2) { 2119 case 0: r = s.read(); break; 2120 case 2: r = s.read(bytes); break; 2121 default: throw new Error(); 2122 } 2123 if (r >= 0) { 2124 // The child sent unexpected output; print it to diagnose 2125 System.out.println("Unexpected child output, to: " + 2126 ((action & 0x1) == 0 ? "getInputStream" : "getErrorStream")); 2127 System.out.println("Child args: " + childArgs); 2128 if ((action & 0x2) == 0) { 2129 System.out.write(r); // Single character 2130 2131 } else { 2132 System.out.write(bytes, 0, r); 2133 } 2134 for (int c = s.read(); c >= 0; c = s.read()) 2135 System.out.write(c); 2136 System.out.println("\nEND Child output."); 2137 } 2138 equal(-1, r); 2139 } catch (IOException ioe) { 2140 String m = ioe.getMessage(); 2141 if (!m.equals("Stream closed")) { 2142 // BufferedInputStream may throw IOE("Stream closed"). 2143 unexpected(ioe); 2144 } 2145 if (matches(m, SPAWNHELPER_FAILURE_MSG)) { 2146 unexpected(ioe); 2147 } 2148 } catch (Throwable t) { unexpected(t); }}}; 2149 2150 thread.start(); 2151 latch.await(); 2152 Thread.sleep(30); 2153 2154 if (s instanceof BufferedInputStream) { 2155 // Wait until after the s.read occurs in "thread" by 2156 // checking when the input stream monitor is acquired 2157 // (BufferedInputStream.read is synchronized) 2158 while (!isLocked((BufferedInputStream) s)) { 2159 Thread.sleep(100); 2160 } 2161 } 2162 p.destroy(); 2163 thread.join(); 2164 } 2165 } catch (Throwable t) { unexpected(t); } 2166 2167 //---------------------------------------------------------------- 2168 // Check that subprocesses which create subprocesses of their 2169 // own do not cause parent to hang waiting for file 2170 // descriptors to be closed. 2171 //---------------------------------------------------------------- 2172 try { 2173 if (Unix.is() 2174 && new File("/bin/bash").exists() 2175 && new File("/bin/sleep").exists()) { 2176 // Notice that we only destroy the process created by us (i.e. 2177 // our child) but not our grandchild (i.e. '/bin/sleep'). So 2178 // pay attention that the grandchild doesn't run too long to 2179 // avoid polluting the process space with useless processes. 2180 // Running the grandchild for 59s should be more than enough. 2181 // A unique (59s) time is needed to avoid killing other sleep processes. 2182 final String[] cmd = { "/bin/bash", "-c", "(/bin/sleep 59)" }; 2183 final String[] cmdkill = { "/bin/bash", "-c", "(/usr/bin/pkill -f \"sleep 59\")" }; 2184 final ProcessBuilder pb = new ProcessBuilder(cmd); 2185 final Process p = pb.start(); 2186 final InputStream stdout = p.getInputStream(); 2187 final InputStream stderr = p.getErrorStream(); 2188 final OutputStream stdin = p.getOutputStream(); 2189 final Thread reader = new Thread() { 2190 public void run() { 2191 try { stdout.read(); } 2192 catch (IOException e) { 2193 // Check that reader failed because stream was 2194 // asynchronously closed. 2195 // e.printStackTrace(); 2196 String msg = e.getMessage(); 2197 if (EnglishUnix.is() && 2198 ! (msg.matches(".*Bad file.*") || 2199 msg.matches(".*Stream closed.*"))) 2200 unexpected(e); 2201 if (matches(msg, SPAWNHELPER_FAILURE_MSG)) { 2202 unexpected(e); 2203 } 2204 } 2205 catch (Throwable t) { unexpected(t); }}}; 2206 reader.setDaemon(true); 2207 reader.start(); 2208 Thread.sleep(100); 2209 p.destroy(); 2210 check(p.waitFor() != 0); 2211 check(p.exitValue() != 0); 2212 // Subprocess is now dead, but file descriptors remain open. 2213 // Make sure the test will fail if we don't manage to close 2214 // the open streams within 30 seconds. Notice that this time 2215 // must be shorter than the sleep time of the grandchild. 2216 Timer t = new Timer("test/java/lang/ProcessBuilder/Basic.java process reaper", true); 2217 t.schedule(new TimerTask() { 2218 public void run() { 2219 fail("Subprocesses which create subprocesses of " + 2220 "their own caused the parent to hang while " + 2221 "waiting for file descriptors to be closed."); 2222 System.exit(-1); 2223 } 2224 }, 30000); 2225 stdout.close(); 2226 stderr.close(); 2227 stdin.close(); 2228 new ProcessBuilder(cmdkill).start(); 2229 // All streams successfully closed so we can cancel the timer. 2230 t.cancel(); 2231 //---------------------------------------------------------- 2232 // There remain unsolved issues with asynchronous close. 2233 // Here's a highly non-portable experiment to demonstrate: 2234 //---------------------------------------------------------- 2235 if (Boolean.getBoolean("wakeupJeff!")) { 2236 System.out.println("wakeupJeff!"); 2237 // Initialize signal handler for INTERRUPT_SIGNAL. 2238 new FileInputStream("/bin/sleep").getChannel().close(); 2239 // Send INTERRUPT_SIGNAL to every thread in this java. 2240 String[] wakeupJeff = { 2241 "/bin/bash", "-c", 2242 "/bin/ps --noheaders -Lfp $PPID | " + 2243 "/usr/bin/perl -nale 'print $F[3]' | " + 2244 // INTERRUPT_SIGNAL == 62 on my machine du jour. 2245 "/usr/bin/xargs kill -62" 2246 }; 2247 new ProcessBuilder(wakeupJeff).start().waitFor(); 2248 // If wakeupJeff worked, reader probably got EBADF. 2249 reader.join(); 2250 } 2251 } 2252 2253 //---------------------------------------------------------------- 2254 // Check the Process toString() method 2255 //---------------------------------------------------------------- 2256 { 2257 List<String> childArgs = new ArrayList<String>(javaChildArgs); 2258 childArgs.add("testIO"); 2259 ProcessBuilder pb = new ProcessBuilder(childArgs); 2260 pb.redirectInput(Redirect.PIPE); 2261 pb.redirectOutput(DISCARD); 2262 pb.redirectError(DISCARD); 2263 final Process p = pb.start(); 2264 // Child process waits until it gets input 2265 String s = p.toString(); 2266 check(s.contains("not exited")); 2267 check(s.contains("pid=" + p.pid() + ",")); 2268 2269 new PrintStream(p.getOutputStream()).print("standard input"); 2270 p.getOutputStream().close(); 2271 2272 // Check the toString after it exits 2273 int exitValue = p.waitFor(); 2274 s = p.toString(); 2275 check(s.contains("pid=" + p.pid() + ",")); 2276 check(s.contains("exitValue=" + exitValue) && 2277 !s.contains("not exited")); 2278 } 2279 } catch (Throwable t) { unexpected(t); } 2280 2281 //---------------------------------------------------------------- 2282 // Attempt to start process with insufficient permissions fails. 2283 //---------------------------------------------------------------- 2284 try { 2285 new File("emptyCommand").delete(); 2286 new FileOutputStream("emptyCommand").close(); 2287 new File("emptyCommand").setExecutable(false); 2288 new ProcessBuilder("./emptyCommand").start(); 2289 fail("Expected IOException not thrown"); 2290 } catch (IOException e) { 2291 new File("./emptyCommand").delete(); 2292 String m = e.getMessage(); 2293 if (EnglishUnix.is() && 2294 ! matches(m, PERMISSION_DENIED_ERROR_MSG)) 2295 unexpected(e); 2296 if (matches(m, SPAWNHELPER_FAILURE_MSG)) { 2297 unexpected(e); 2298 } 2299 } catch (Throwable t) { unexpected(t); } 2300 2301 new File("emptyCommand").delete(); 2302 2303 //---------------------------------------------------------------- 2304 // Check that Process.isAlive() & 2305 // Process.waitFor(0, TimeUnit.MILLISECONDS) work as expected. 2306 //---------------------------------------------------------------- 2307 try { 2308 List<String> childArgs = getSleepArgs(); 2309 final Process p = new ProcessBuilder(childArgs).start(); 2310 long start = System.nanoTime(); 2311 if (!p.isAlive() || p.waitFor(0, TimeUnit.MILLISECONDS)) { 2312 fail("Test failed: Process exited prematurely"); 2313 } 2314 long end = System.nanoTime(); 2315 // give waitFor(timeout) a wide berth (2s) 2316 System.out.printf(" waitFor process: delta: %d%n",(end - start) ); 2317 2318 if ((end - start) > TimeUnit.SECONDS.toNanos(2)) 2319 fail("Test failed: waitFor took too long (" + (end - start) + "ns)"); 2320 2321 p.destroy(); 2322 p.waitFor(); 2323 2324 if (p.isAlive() || 2325 !p.waitFor(0, TimeUnit.MILLISECONDS)) 2326 { 2327 fail("Test failed: Process still alive - please terminate " + 2328 p.toString() + " manually"); 2329 } 2330 } catch (Throwable t) { unexpected(t); } 2331 2332 //---------------------------------------------------------------- 2333 // Check that Process.waitFor(timeout, TimeUnit.MILLISECONDS) 2334 // works as expected. 2335 //---------------------------------------------------------------- 2336 try { 2337 List<String> childArgs = getSleepArgs(); 2338 final Process p = new ProcessBuilder(childArgs).start(); 2339 long start = System.nanoTime(); 2340 2341 if (p.waitFor(10, TimeUnit.MILLISECONDS)) { 2342 var msg = "External sleep process terminated early: exitValue: %d, (%dns)%n" 2343 .formatted(p.exitValue(), (System.nanoTime() - start)); 2344 fail(msg); 2345 } else { 2346 long end = System.nanoTime(); 2347 if ((end - start) < TimeUnit.MILLISECONDS.toNanos(10)) 2348 fail("Test failed: waitFor didn't take long enough (" + (end - start) + "ns)"); 2349 } 2350 p.destroy(); 2351 } catch (Throwable t) { unexpected(t); } 2352 2353 //---------------------------------------------------------------- 2354 // Check that Process.waitFor(timeout, TimeUnit.MILLISECONDS) 2355 // interrupt works as expected, if interrupted while waiting. 2356 //---------------------------------------------------------------- 2357 try { 2358 List<String> childArgs = getSleepArgs(); 2359 final Process p = new ProcessBuilder(childArgs).start(); 2360 final long start = System.nanoTime(); 2361 final CountDownLatch aboutToWaitFor = new CountDownLatch(1); 2362 2363 final Thread thread = new Thread() { 2364 public void run() { 2365 try { 2366 aboutToWaitFor.countDown(); 2367 Thread.currentThread().interrupt(); 2368 boolean result = p.waitFor(30L * 1000L, TimeUnit.MILLISECONDS); 2369 fail("waitFor() wasn't interrupted, its return value was: " + result); 2370 } catch (InterruptedException success) { 2371 } catch (Throwable t) { unexpected(t); } 2372 } 2373 }; 2374 2375 thread.start(); 2376 aboutToWaitFor.await(); 2377 thread.interrupt(); 2378 thread.join(10L * 1000L); 2379 check(millisElapsedSince(start) < 10L * 1000L); 2380 check(!thread.isAlive()); 2381 p.destroy(); 2382 } catch (Throwable t) { unexpected(t); } 2383 2384 //---------------------------------------------------------------- 2385 // Check that Process.waitFor(Long.MAX_VALUE, TimeUnit.MILLISECONDS) 2386 // interrupt works as expected, if interrupted while waiting. 2387 //---------------------------------------------------------------- 2388 try { 2389 List<String> childArgs = getSleepArgs(); 2390 final Process p = new ProcessBuilder(childArgs).start(); 2391 final long start = System.nanoTime(); 2392 final CountDownLatch aboutToWaitFor = new CountDownLatch(1); 2393 2394 final Thread thread = new Thread() { 2395 public void run() { 2396 try { 2397 aboutToWaitFor.countDown(); 2398 Thread.currentThread().interrupt(); 2399 boolean result = p.waitFor(Long.MAX_VALUE, TimeUnit.MILLISECONDS); 2400 fail("waitFor() wasn't interrupted, its return value was: " + result); 2401 } catch (InterruptedException success) { 2402 } catch (Throwable t) { unexpected(t); } 2403 } 2404 }; 2405 2406 thread.start(); 2407 aboutToWaitFor.await(); 2408 thread.interrupt(); 2409 thread.join(10L * 1000L); 2410 check(millisElapsedSince(start) < 10L * 1000L); 2411 check(!thread.isAlive()); 2412 p.destroy(); 2413 } catch (Throwable t) { unexpected(t); } 2414 2415 //---------------------------------------------------------------- 2416 // Check that Process.waitFor(timeout, TimeUnit.MILLISECONDS) 2417 // interrupt works as expected, if interrupted before waiting. 2418 //---------------------------------------------------------------- 2419 try { 2420 List<String> childArgs = getSleepArgs(); 2421 final Process p = new ProcessBuilder(childArgs).start(); 2422 final long start = System.nanoTime(); 2423 final CountDownLatch threadStarted = new CountDownLatch(1); 2424 2425 final Thread thread = new Thread() { 2426 public void run() { 2427 try { 2428 threadStarted.countDown(); 2429 do { Thread.yield(); } 2430 while (!Thread.currentThread().isInterrupted()); 2431 boolean result = p.waitFor(30L * 1000L, TimeUnit.MILLISECONDS); 2432 fail("waitFor() wasn't interrupted, its return value was: " + result); 2433 } catch (InterruptedException success) { 2434 } catch (Throwable t) { unexpected(t); } 2435 } 2436 }; 2437 2438 thread.start(); 2439 threadStarted.await(); 2440 thread.interrupt(); 2441 thread.join(10L * 1000L); 2442 check(millisElapsedSince(start) < 10L * 1000L); 2443 check(!thread.isAlive()); 2444 p.destroy(); 2445 } catch (Throwable t) { unexpected(t); } 2446 2447 //---------------------------------------------------------------- 2448 // Check that Process.waitFor(timeout, null) throws NPE. 2449 //---------------------------------------------------------------- 2450 try { 2451 List<String> childArgs = getSleepArgs(); 2452 final Process p = new ProcessBuilder(childArgs).start(); 2453 THROWS(NullPointerException.class, 2454 () -> p.waitFor(10L, null)); 2455 THROWS(NullPointerException.class, 2456 () -> p.waitFor(0L, null)); 2457 THROWS(NullPointerException.class, 2458 () -> p.waitFor(-1L, null)); 2459 // Terminate process and recheck after it exits 2460 p.destroy(); 2461 p.waitFor(); 2462 THROWS(NullPointerException.class, 2463 () -> p.waitFor(10L, null)); 2464 THROWS(NullPointerException.class, 2465 () -> p.waitFor(0L, null)); 2466 THROWS(NullPointerException.class, 2467 () -> p.waitFor(-1L, null)); 2468 } catch (Throwable t) { unexpected(t); } 2469 2470 //---------------------------------------------------------------- 2471 // Check that default implementation of Process.waitFor(timeout, null) throws NPE. 2472 //---------------------------------------------------------------- 2473 try { 2474 List<String> childArgs = getSleepArgs(); 2475 final Process proc = new ProcessBuilder(childArgs).start(); 2476 final DelegatingProcess p = new DelegatingProcess(proc); 2477 2478 THROWS(NullPointerException.class, 2479 () -> p.waitFor(10L, null)); 2480 THROWS(NullPointerException.class, 2481 () -> p.waitFor(0L, null)); 2482 THROWS(NullPointerException.class, 2483 () -> p.waitFor(-1L, null)); 2484 // Terminate process and recheck after it exits 2485 p.destroy(); 2486 p.waitFor(); 2487 THROWS(NullPointerException.class, 2488 () -> p.waitFor(10L, null)); 2489 THROWS(NullPointerException.class, 2490 () -> p.waitFor(0L, null)); 2491 THROWS(NullPointerException.class, 2492 () -> p.waitFor(-1L, null)); 2493 } catch (Throwable t) { unexpected(t); } 2494 2495 //---------------------------------------------------------------- 2496 // Check the default implementation for 2497 // Process.waitFor(long, TimeUnit) 2498 //---------------------------------------------------------------- 2499 try { 2500 List<String> childArgs = getSleepArgs(); 2501 final Process proc = new ProcessBuilder(childArgs).start(); 2502 DelegatingProcess p = new DelegatingProcess(proc); 2503 long start = System.nanoTime(); 2504 2505 if (p.waitFor(1000, TimeUnit.MILLISECONDS)) { 2506 var msg = "External sleep process terminated early: exitValue: %02x, (%dns)" 2507 .formatted(p.exitValue(), (System.nanoTime() - start)); 2508 fail(msg); 2509 } else { 2510 long end = System.nanoTime(); 2511 if ((end - start) < 500000000) 2512 fail("Test failed: waitFor didn't take long enough (" + (end - start) + "ns)"); 2513 } 2514 p.destroy(); 2515 2516 p.waitFor(1000, TimeUnit.MILLISECONDS); 2517 } catch (Throwable t) { unexpected(t); } 2518 } 2519 2520 // Path to native executables, if any 2521 private static final String TEST_NATIVEPATH = System.getProperty("test.nativepath"); 2522 2523 // Path where "sleep" program may be found" or null 2524 private static final Path SLEEP_PATH = initSleepPath(); 2525 2526 /** 2527 * Compute the Path to a sleep executable. 2528 * @return a Path to sleep or BasicSleep(.exe) or null if none 2529 */ 2530 private static Path initSleepPath() { 2531 if (Windows.is() && TEST_NATIVEPATH != null) { 2532 // exeBasicSleep is equivalent to sleep on Unix 2533 Path exePath = Path.of(TEST_NATIVEPATH).resolve("BasicSleep.exe"); 2534 if (Files.isExecutable(exePath)) { 2535 return exePath; 2536 } 2537 } 2538 2539 List<String> binPaths = List.of("/bin", "/usr/bin"); 2540 for (String dir : binPaths) { 2541 Path exePath = Path.of(dir).resolve("sleep"); 2542 if (Files.isExecutable(exePath)) { 2543 return exePath; 2544 } 2545 } 2546 return null; 2547 } 2548 2549 /** 2550 * Return the list of process arguments for a child to sleep 10 minutes (600 seconds). 2551 * 2552 * @return A list of process arguments to sleep 10 minutes. 2553 */ 2554 private static List<String> getSleepArgs() { 2555 List<String> childArgs = null; 2556 if (SLEEP_PATH != null) { 2557 childArgs = List.of(SLEEP_PATH.toString(), "600"); 2558 } else { 2559 // Fallback to the JavaChild ; its 'sleep' command is for 10 minutes. 2560 // The fallback the Java$Child is used if the test is run without building 2561 // the BasicSleep native executable (for Windows). 2562 childArgs = new ArrayList<>(javaChildArgs); 2563 childArgs.add("sleep"); 2564 System.out.println("Sleep not found, fallback to JavaChild: " + childArgs); 2565 } 2566 return childArgs; 2567 } 2568 2569 static void closeStreams(Process p) { 2570 try { 2571 p.getOutputStream().close(); 2572 p.getInputStream().close(); 2573 p.getErrorStream().close(); 2574 } catch (Throwable t) { unexpected(t); } 2575 } 2576 2577 private static class StreamAccumulator extends Thread { 2578 private final InputStream is; 2579 private final StringBuilder sb = new StringBuilder(); 2580 private Throwable throwable = null; 2581 2582 public String result () throws Throwable { 2583 if (throwable != null) 2584 throw throwable; 2585 return sb.toString(); 2586 } 2587 2588 StreamAccumulator (InputStream is) { 2589 this.is = is; 2590 } 2591 2592 public void run() { 2593 try { 2594 Reader r = new InputStreamReader(is); 2595 char[] buf = new char[4096]; 2596 int n; 2597 while ((n = r.read(buf)) > 0) { 2598 sb.append(buf,0,n); 2599 } 2600 } catch (Throwable t) { 2601 throwable = t; 2602 } finally { 2603 try { is.close(); } 2604 catch (Throwable t) { throwable = t; } 2605 } 2606 } 2607 } 2608 2609 static ProcessResults run(ProcessBuilder pb) { 2610 try { 2611 return run(pb.start()); 2612 } catch (Throwable t) { unexpected(t); return null; } 2613 } 2614 2615 private static ProcessResults run(Process p) { 2616 Throwable throwable = null; 2617 int exitValue = -1; 2618 String out = ""; 2619 String err = ""; 2620 2621 StreamAccumulator outAccumulator = 2622 new StreamAccumulator(p.getInputStream()); 2623 StreamAccumulator errAccumulator = 2624 new StreamAccumulator(p.getErrorStream()); 2625 2626 try { 2627 outAccumulator.start(); 2628 errAccumulator.start(); 2629 2630 exitValue = p.waitFor(); 2631 2632 outAccumulator.join(); 2633 errAccumulator.join(); 2634 2635 out = outAccumulator.result(); 2636 err = errAccumulator.result(); 2637 } catch (Throwable t) { 2638 throwable = t; 2639 } 2640 2641 return new ProcessResults(out, err, exitValue, throwable); 2642 } 2643 2644 //---------------------------------------------------------------- 2645 // Results of a command 2646 //---------------------------------------------------------------- 2647 private static class ProcessResults { 2648 private final String out; 2649 private final String err; 2650 private final int exitValue; 2651 private final Throwable throwable; 2652 2653 public ProcessResults(String out, 2654 String err, 2655 int exitValue, 2656 Throwable throwable) { 2657 this.out = out; 2658 this.err = err; 2659 this.exitValue = exitValue; 2660 this.throwable = throwable; 2661 } 2662 2663 public String out() { return out; } 2664 public String err() { return err; } 2665 public int exitValue() { return exitValue; } 2666 public Throwable throwable() { return throwable; } 2667 2668 public String toString() { 2669 StringBuilder sb = new StringBuilder(); 2670 sb.append("<STDOUT>\n" + out() + "</STDOUT>\n") 2671 .append("<STDERR>\n" + err() + "</STDERR>\n") 2672 .append("exitValue = " + exitValue + "\n"); 2673 if (throwable != null) 2674 sb.append(throwable.getStackTrace()); 2675 return sb.toString(); 2676 } 2677 } 2678 2679 //--------------------- Infrastructure --------------------------- 2680 static volatile int passed = 0, failed = 0; 2681 static void pass() {passed++;} 2682 static void fail() {failed++; Thread.dumpStack();} 2683 static void fail(String msg) {System.err.println(msg); fail();} 2684 static void unexpected(Throwable t) {failed++; t.printStackTrace();} 2685 static void check(boolean cond) {if (cond) pass(); else fail();} 2686 static void check(boolean cond, String m) {if (cond) pass(); else fail(m);} 2687 static void equal(Object x, Object y) { 2688 if (x == null ? y == null : x.equals(y)) pass(); 2689 else fail(">'" + x + "'<" + " not equal to " + "'" + y + "'");} 2690 2691 public static void main(String[] args) throws Throwable { 2692 try {realMain(args);} catch (Throwable t) {unexpected(t);} 2693 System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); 2694 if (failed > 0) throw new AssertionError("Some tests failed");} 2695 interface Fun {void f() throws Throwable;} 2696 static void THROWS(Class<? extends Throwable> k, Fun... fs) { 2697 for (Fun f : fs) 2698 try { f.f(); fail("Expected " + k.getName() + " not thrown"); } 2699 catch (Throwable t) { 2700 if (k.isAssignableFrom(t.getClass())) pass(); 2701 else unexpected(t);}} 2702 2703 static boolean isLocked(BufferedInputStream bis) throws Exception { 2704 return new Thread() { 2705 volatile boolean unlocked; 2706 2707 @Override 2708 public void run() { 2709 synchronized (bis) { unlocked = true; } 2710 } 2711 2712 boolean isLocked() throws InterruptedException { 2713 start(); 2714 join(10); 2715 return !unlocked; 2716 } 2717 }.isLocked(); 2718 } 2719 }