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