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