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 }