| /* |
| * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* |
| * @test |
| * @bug 4199068 4738465 4937983 4930681 4926230 4931433 4932663 4986689 |
| * 5026830 5023243 5070673 4052517 4811767 6192449 6397034 6413313 |
| * 6464154 6523983 6206031 4960438 6631352 6631966 6850957 6850958 |
| * 4947220 7018606 7034570 4244896 5049299 |
| * @summary Basic tests for Process and Environment Variable code |
| * @run main/othervm/timeout=300 Basic |
| * @run main/othervm/timeout=300 -Djdk.lang.Process.launchMechanism=fork Basic |
| * @author Martin Buchholz |
| */ |
| |
| import java.lang.ProcessBuilder.Redirect; |
| import static java.lang.ProcessBuilder.Redirect.*; |
| |
| import java.io.*; |
| import java.lang.reflect.Field; |
| import java.util.*; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.security.*; |
| import sun.misc.Unsafe; |
| import java.util.regex.Pattern; |
| import java.util.regex.Matcher; |
| import static java.lang.System.getenv; |
| import static java.lang.System.out; |
| import static java.lang.Boolean.TRUE; |
| import static java.util.AbstractMap.SimpleImmutableEntry; |
| |
| public class Basic { |
| |
| /* used for Windows only */ |
| static final String systemRoot = System.getenv("SystemRoot"); |
| |
| /* used for Mac OS X only */ |
| static final String cfUserTextEncoding = System.getenv("__CF_USER_TEXT_ENCODING"); |
| |
| private static String commandOutput(Reader r) throws Throwable { |
| StringBuilder sb = new StringBuilder(); |
| int c; |
| while ((c = r.read()) > 0) |
| if (c != '\r') |
| sb.append((char) c); |
| return sb.toString(); |
| } |
| |
| private static String commandOutput(Process p) throws Throwable { |
| check(p.getInputStream() == p.getInputStream()); |
| check(p.getOutputStream() == p.getOutputStream()); |
| check(p.getErrorStream() == p.getErrorStream()); |
| Reader r = new InputStreamReader(p.getInputStream(),"UTF-8"); |
| String output = commandOutput(r); |
| equal(p.waitFor(), 0); |
| equal(p.exitValue(), 0); |
| return output; |
| } |
| |
| private static String commandOutput(ProcessBuilder pb) { |
| try { |
| return commandOutput(pb.start()); |
| } catch (Throwable t) { |
| String commandline = ""; |
| for (String arg : pb.command()) |
| commandline += " " + arg; |
| System.out.println("Exception trying to run process: " + commandline); |
| unexpected(t); |
| return ""; |
| } |
| } |
| |
| private static String commandOutput(String...command) { |
| try { |
| return commandOutput(Runtime.getRuntime().exec(command)); |
| } catch (Throwable t) { |
| String commandline = ""; |
| for (String arg : command) |
| commandline += " " + arg; |
| System.out.println("Exception trying to run process: " + commandline); |
| unexpected(t); |
| return ""; |
| } |
| } |
| |
| private static void checkCommandOutput(ProcessBuilder pb, |
| String expected, |
| String failureMsg) { |
| String got = commandOutput(pb); |
| check(got.equals(expected), |
| failureMsg + "\n" + |
| "Expected: \"" + expected + "\"\n" + |
| "Got: \"" + got + "\""); |
| } |
| |
| private static String absolutifyPath(String path) { |
| StringBuilder sb = new StringBuilder(); |
| for (String file : path.split(File.pathSeparator)) { |
| if (sb.length() != 0) |
| sb.append(File.pathSeparator); |
| sb.append(new File(file).getAbsolutePath()); |
| } |
| return sb.toString(); |
| } |
| |
| // compare windows-style, by canonicalizing to upper case, |
| // not lower case as String.compareToIgnoreCase does |
| private static class WindowsComparator |
| implements Comparator<String> { |
| public int compare(String x, String y) { |
| return x.toUpperCase(Locale.US) |
| .compareTo(y.toUpperCase(Locale.US)); |
| } |
| } |
| |
| private static String sortedLines(String lines) { |
| String[] arr = lines.split("\n"); |
| List<String> ls = new ArrayList<String>(); |
| for (String s : arr) |
| ls.add(s); |
| Collections.sort(ls, new WindowsComparator()); |
| StringBuilder sb = new StringBuilder(); |
| for (String s : ls) |
| sb.append(s + "\n"); |
| return sb.toString(); |
| } |
| |
| private static void compareLinesIgnoreCase(String lines1, String lines2) { |
| if (! (sortedLines(lines1).equalsIgnoreCase(sortedLines(lines2)))) { |
| String dashes = |
| "-----------------------------------------------------"; |
| out.println(dashes); |
| out.print(sortedLines(lines1)); |
| out.println(dashes); |
| out.print(sortedLines(lines2)); |
| out.println(dashes); |
| out.println("sizes: " + sortedLines(lines1).length() + |
| " " + sortedLines(lines2).length()); |
| |
| fail("Sorted string contents differ"); |
| } |
| } |
| |
| private static final Runtime runtime = Runtime.getRuntime(); |
| |
| private static final String[] winEnvCommand = {"cmd.exe", "/c", "set"}; |
| |
| private static String winEnvFilter(String env) { |
| return env.replaceAll("\r", "") |
| .replaceAll("(?m)^(?:COMSPEC|PROMPT|PATHEXT)=.*\n",""); |
| } |
| |
| private static String unixEnvProg() { |
| return new File("/usr/bin/env").canExecute() ? "/usr/bin/env" |
| : "/bin/env"; |
| } |
| |
| private static String nativeEnv(String[] env) { |
| try { |
| if (Windows.is()) { |
| return winEnvFilter |
| (commandOutput(runtime.exec(winEnvCommand, env))); |
| } else { |
| return commandOutput(runtime.exec(unixEnvProg(), env)); |
| } |
| } catch (Throwable t) { throw new Error(t); } |
| } |
| |
| private static String nativeEnv(ProcessBuilder pb) { |
| try { |
| if (Windows.is()) { |
| pb.command(winEnvCommand); |
| return winEnvFilter(commandOutput(pb)); |
| } else { |
| pb.command(new String[]{unixEnvProg()}); |
| return commandOutput(pb); |
| } |
| } catch (Throwable t) { throw new Error(t); } |
| } |
| |
| private static void checkSizes(Map<String,String> environ, int size) { |
| try { |
| equal(size, environ.size()); |
| equal(size, environ.entrySet().size()); |
| equal(size, environ.keySet().size()); |
| equal(size, environ.values().size()); |
| |
| boolean isEmpty = (size == 0); |
| equal(isEmpty, environ.isEmpty()); |
| equal(isEmpty, environ.entrySet().isEmpty()); |
| equal(isEmpty, environ.keySet().isEmpty()); |
| equal(isEmpty, environ.values().isEmpty()); |
| } catch (Throwable t) { unexpected(t); } |
| } |
| |
| private interface EnvironmentFrobber { |
| void doIt(Map<String,String> environ); |
| } |
| |
| private static void testVariableDeleter(EnvironmentFrobber fooDeleter) { |
| try { |
| Map<String,String> environ = new ProcessBuilder().environment(); |
| environ.put("Foo", "BAAR"); |
| fooDeleter.doIt(environ); |
| equal(environ.get("Foo"), null); |
| equal(environ.remove("Foo"), null); |
| } catch (Throwable t) { unexpected(t); } |
| } |
| |
| private static void testVariableAdder(EnvironmentFrobber fooAdder) { |
| try { |
| Map<String,String> environ = new ProcessBuilder().environment(); |
| environ.remove("Foo"); |
| fooAdder.doIt(environ); |
| equal(environ.get("Foo"), "Bahrein"); |
| } catch (Throwable t) { unexpected(t); } |
| } |
| |
| private static void testVariableModifier(EnvironmentFrobber fooModifier) { |
| try { |
| Map<String,String> environ = new ProcessBuilder().environment(); |
| environ.put("Foo","OldValue"); |
| fooModifier.doIt(environ); |
| equal(environ.get("Foo"), "NewValue"); |
| } catch (Throwable t) { unexpected(t); } |
| } |
| |
| private static void printUTF8(String s) throws IOException { |
| out.write(s.getBytes("UTF-8")); |
| } |
| |
| private static String getenvAsString(Map<String,String> environment) { |
| StringBuilder sb = new StringBuilder(); |
| environment = new TreeMap<>(environment); |
| for (Map.Entry<String,String> e : environment.entrySet()) |
| // Ignore magic environment variables added by the launcher |
| if (! e.getKey().equals("NLSPATH") && |
| ! e.getKey().equals("XFILESEARCHPATH") && |
| ! e.getKey().equals("LD_LIBRARY_PATH")) |
| sb.append(e.getKey()) |
| .append('=') |
| .append(e.getValue()) |
| .append(','); |
| return sb.toString(); |
| } |
| |
| static void print4095(OutputStream s, byte b) throws Throwable { |
| byte[] bytes = new byte[4095]; |
| Arrays.fill(bytes, b); |
| s.write(bytes); // Might hang! |
| } |
| |
| static void checkPermissionDenied(ProcessBuilder pb) { |
| try { |
| pb.start(); |
| fail("Expected IOException not thrown"); |
| } catch (IOException e) { |
| String m = e.getMessage(); |
| if (EnglishUnix.is() && |
| ! matches(m, "Permission denied")) |
| unexpected(e); |
| } catch (Throwable t) { unexpected(t); } |
| } |
| |
| public static class JavaChild { |
| public static void main(String args[]) throws Throwable { |
| String action = args[0]; |
| if (action.equals("sleep")) { |
| Thread.sleep(10 * 60 * 1000L); |
| } else if (action.equals("testIO")) { |
| String expected = "standard input"; |
| char[] buf = new char[expected.length()+1]; |
| int n = new InputStreamReader(System.in).read(buf,0,buf.length); |
| if (n != expected.length()) |
| System.exit(5); |
| if (! new String(buf,0,n).equals(expected)) |
| System.exit(5); |
| System.err.print("standard error"); |
| System.out.print("standard output"); |
| } else if (action.equals("testInheritIO") |
| || action.equals("testRedirectInherit")) { |
| List<String> childArgs = new ArrayList<String>(javaChildArgs); |
| childArgs.add("testIO"); |
| ProcessBuilder pb = new ProcessBuilder(childArgs); |
| if (action.equals("testInheritIO")) |
| pb.inheritIO(); |
| else |
| redirectIO(pb, INHERIT, INHERIT, INHERIT); |
| ProcessResults r = run(pb); |
| if (! r.out().equals("")) |
| System.exit(7); |
| if (! r.err().equals("")) |
| System.exit(8); |
| if (r.exitValue() != 0) |
| System.exit(9); |
| } else if (action.equals("System.getenv(String)")) { |
| String val = System.getenv(args[1]); |
| printUTF8(val == null ? "null" : val); |
| } else if (action.equals("System.getenv(\\u1234)")) { |
| String val = System.getenv("\u1234"); |
| printUTF8(val == null ? "null" : val); |
| } else if (action.equals("System.getenv()")) { |
| printUTF8(getenvAsString(System.getenv())); |
| } else if (action.equals("ArrayOOME")) { |
| Object dummy; |
| switch(new Random().nextInt(3)) { |
| case 0: dummy = new Integer[Integer.MAX_VALUE]; break; |
| case 1: dummy = new double[Integer.MAX_VALUE]; break; |
| case 2: dummy = new byte[Integer.MAX_VALUE][]; break; |
| default: throw new InternalError(); |
| } |
| } else if (action.equals("pwd")) { |
| printUTF8(new File(System.getProperty("user.dir")) |
| .getCanonicalPath()); |
| } else if (action.equals("print4095")) { |
| print4095(System.out, (byte) '!'); |
| print4095(System.err, (byte) 'E'); |
| System.exit(5); |
| } else if (action.equals("OutErr")) { |
| // You might think the system streams would be |
| // buffered, and in fact they are implemented using |
| // BufferedOutputStream, but each and every print |
| // causes immediate operating system I/O. |
| System.out.print("out"); |
| System.err.print("err"); |
| System.out.print("out"); |
| System.err.print("err"); |
| } else if (action.equals("null PATH")) { |
| equal(System.getenv("PATH"), null); |
| check(new File("/bin/true").exists()); |
| check(new File("/bin/false").exists()); |
| ProcessBuilder pb1 = new ProcessBuilder(); |
| ProcessBuilder pb2 = new ProcessBuilder(); |
| pb2.environment().put("PATH", "anyOldPathIgnoredAnyways"); |
| ProcessResults r; |
| |
| for (final ProcessBuilder pb : |
| new ProcessBuilder[] {pb1, pb2}) { |
| pb.command("true"); |
| equal(run(pb).exitValue(), True.exitValue()); |
| |
| pb.command("false"); |
| equal(run(pb).exitValue(), False.exitValue()); |
| } |
| |
| if (failed != 0) throw new Error("null PATH"); |
| } else if (action.equals("PATH search algorithm")) { |
| equal(System.getenv("PATH"), "dir1:dir2:"); |
| check(new File("/bin/true").exists()); |
| check(new File("/bin/false").exists()); |
| String[] cmd = {"prog"}; |
| ProcessBuilder pb1 = new ProcessBuilder(cmd); |
| ProcessBuilder pb2 = new ProcessBuilder(cmd); |
| ProcessBuilder pb3 = new ProcessBuilder(cmd); |
| pb2.environment().put("PATH", "anyOldPathIgnoredAnyways"); |
| pb3.environment().remove("PATH"); |
| |
| for (final ProcessBuilder pb : |
| new ProcessBuilder[] {pb1, pb2, pb3}) { |
| try { |
| // Not on PATH at all; directories don't exist |
| try { |
| pb.start(); |
| fail("Expected IOException not thrown"); |
| } catch (IOException e) { |
| String m = e.getMessage(); |
| if (EnglishUnix.is() && |
| ! matches(m, "No such file")) |
| unexpected(e); |
| } catch (Throwable t) { unexpected(t); } |
| |
| // Not on PATH at all; directories exist |
| new File("dir1").mkdirs(); |
| new File("dir2").mkdirs(); |
| try { |
| pb.start(); |
| fail("Expected IOException not thrown"); |
| } catch (IOException e) { |
| String m = e.getMessage(); |
| if (EnglishUnix.is() && |
| ! matches(m, "No such file")) |
| unexpected(e); |
| } catch (Throwable t) { unexpected(t); } |
| |
| // Can't execute a directory -- permission denied |
| // Report EACCES errno |
| new File("dir1/prog").mkdirs(); |
| checkPermissionDenied(pb); |
| |
| // continue searching if EACCES |
| copy("/bin/true", "dir2/prog"); |
| equal(run(pb).exitValue(), True.exitValue()); |
| new File("dir1/prog").delete(); |
| new File("dir2/prog").delete(); |
| |
| new File("dir2/prog").mkdirs(); |
| copy("/bin/true", "dir1/prog"); |
| equal(run(pb).exitValue(), True.exitValue()); |
| |
| // Check empty PATH component means current directory. |
| // |
| // While we're here, let's test different kinds of |
| // Unix executables, and PATH vs explicit searching. |
| new File("dir1/prog").delete(); |
| new File("dir2/prog").delete(); |
| for (String[] command : |
| new String[][] { |
| new String[] {"./prog"}, |
| cmd}) { |
| pb.command(command); |
| File prog = new File("./prog"); |
| // "Normal" binaries |
| copy("/bin/true", "./prog"); |
| equal(run(pb).exitValue(), |
| True.exitValue()); |
| copy("/bin/false", "./prog"); |
| equal(run(pb).exitValue(), |
| False.exitValue()); |
| prog.delete(); |
| // Interpreter scripts with #! |
| setFileContents(prog, "#!/bin/true\n"); |
| prog.setExecutable(true); |
| equal(run(pb).exitValue(), |
| True.exitValue()); |
| prog.delete(); |
| setFileContents(prog, "#!/bin/false\n"); |
| prog.setExecutable(true); |
| equal(run(pb).exitValue(), |
| False.exitValue()); |
| // Traditional shell scripts without #! |
| setFileContents(prog, "exec /bin/true\n"); |
| prog.setExecutable(true); |
| equal(run(pb).exitValue(), |
| True.exitValue()); |
| prog.delete(); |
| setFileContents(prog, "exec /bin/false\n"); |
| prog.setExecutable(true); |
| equal(run(pb).exitValue(), |
| False.exitValue()); |
| prog.delete(); |
| } |
| |
| // Test Unix interpreter scripts |
| File dir1Prog = new File("dir1/prog"); |
| dir1Prog.delete(); |
| pb.command(new String[] {"prog", "world"}); |
| setFileContents(dir1Prog, "#!/bin/echo hello\n"); |
| checkPermissionDenied(pb); |
| dir1Prog.setExecutable(true); |
| equal(run(pb).out(), "hello dir1/prog world\n"); |
| equal(run(pb).exitValue(), True.exitValue()); |
| dir1Prog.delete(); |
| pb.command(cmd); |
| |
| // Test traditional shell scripts without #! |
| setFileContents(dir1Prog, "/bin/echo \"$@\"\n"); |
| pb.command(new String[] {"prog", "hello", "world"}); |
| checkPermissionDenied(pb); |
| dir1Prog.setExecutable(true); |
| equal(run(pb).out(), "hello world\n"); |
| equal(run(pb).exitValue(), True.exitValue()); |
| dir1Prog.delete(); |
| pb.command(cmd); |
| |
| // If prog found on both parent and child's PATH, |
| // parent's is used. |
| new File("dir1/prog").delete(); |
| new File("dir2/prog").delete(); |
| new File("prog").delete(); |
| new File("dir3").mkdirs(); |
| copy("/bin/true", "dir1/prog"); |
| copy("/bin/false", "dir3/prog"); |
| pb.environment().put("PATH","dir3"); |
| equal(run(pb).exitValue(), True.exitValue()); |
| copy("/bin/true", "dir3/prog"); |
| copy("/bin/false", "dir1/prog"); |
| equal(run(pb).exitValue(), False.exitValue()); |
| |
| } finally { |
| // cleanup |
| new File("dir1/prog").delete(); |
| new File("dir2/prog").delete(); |
| new File("dir3/prog").delete(); |
| new File("dir1").delete(); |
| new File("dir2").delete(); |
| new File("dir3").delete(); |
| new File("prog").delete(); |
| } |
| } |
| |
| if (failed != 0) throw new Error("PATH search algorithm"); |
| } |
| else throw new Error("JavaChild invocation error"); |
| } |
| } |
| |
| private static void copy(String src, String dst) { |
| system("/bin/cp", "-fp", src, dst); |
| } |
| |
| private static void system(String... command) { |
| try { |
| ProcessBuilder pb = new ProcessBuilder(command); |
| ProcessResults r = run(pb.start()); |
| equal(r.exitValue(), 0); |
| equal(r.out(), ""); |
| equal(r.err(), ""); |
| } catch (Throwable t) { unexpected(t); } |
| } |
| |
| private static String javaChildOutput(ProcessBuilder pb, String...args) { |
| List<String> list = new ArrayList<String>(javaChildArgs); |
| for (String arg : args) |
| list.add(arg); |
| pb.command(list); |
| return commandOutput(pb); |
| } |
| |
| private static String getenvInChild(ProcessBuilder pb) { |
| return javaChildOutput(pb, "System.getenv()"); |
| } |
| |
| private static String getenvInChild1234(ProcessBuilder pb) { |
| return javaChildOutput(pb, "System.getenv(\\u1234)"); |
| } |
| |
| private static String getenvInChild(ProcessBuilder pb, String name) { |
| return javaChildOutput(pb, "System.getenv(String)", name); |
| } |
| |
| private static String pwdInChild(ProcessBuilder pb) { |
| return javaChildOutput(pb, "pwd"); |
| } |
| |
| private static final String javaExe = |
| System.getProperty("java.home") + |
| File.separator + "bin" + File.separator + "java"; |
| |
| private static final String classpath = |
| System.getProperty("java.class.path"); |
| |
| private static final List<String> javaChildArgs = |
| Arrays.asList(new String[] |
| { javaExe, "-classpath", absolutifyPath(classpath), |
| "Basic$JavaChild"}); |
| |
| private static void testEncoding(String encoding, String tested) { |
| try { |
| // If round trip conversion works, should be able to set env vars |
| // correctly in child. |
| if (new String(tested.getBytes()).equals(tested)) { |
| out.println("Testing " + encoding + " environment values"); |
| ProcessBuilder pb = new ProcessBuilder(); |
| pb.environment().put("ASCIINAME",tested); |
| equal(getenvInChild(pb,"ASCIINAME"), tested); |
| } |
| } catch (Throwable t) { unexpected(t); } |
| } |
| |
| static class Windows { |
| public static boolean is() { return is; } |
| private static final boolean is = |
| System.getProperty("os.name").startsWith("Windows"); |
| } |
| |
| static class Unix { |
| public static boolean is() { return is; } |
| private static final boolean is = |
| (! Windows.is() && |
| new File("/bin/sh").exists() && |
| new File("/bin/true").exists() && |
| new File("/bin/false").exists()); |
| } |
| |
| static class UnicodeOS { |
| public static boolean is() { return is; } |
| private static final String osName = System.getProperty("os.name"); |
| private static final boolean is = |
| // MacOS X would probably also qualify |
| osName.startsWith("Windows") && |
| ! osName.startsWith("Windows 9") && |
| ! osName.equals("Windows Me"); |
| } |
| |
| static class MacOSX { |
| public static boolean is() { return is; } |
| private static final String osName = System.getProperty("os.name"); |
| private static final boolean is = osName.contains("OS X"); |
| } |
| |
| static class True { |
| public static int exitValue() { return 0; } |
| } |
| |
| private static class False { |
| public static int exitValue() { return exitValue; } |
| private static final int exitValue = exitValue0(); |
| private static int exitValue0() { |
| // /bin/false returns an *unspecified* non-zero number. |
| try { |
| if (! Unix.is()) |
| return -1; |
| else { |
| int rc = new ProcessBuilder("/bin/false") |
| .start().waitFor(); |
| check(rc != 0); |
| return rc; |
| } |
| } catch (Throwable t) { unexpected(t); return -1; } |
| } |
| } |
| |
| static class EnglishUnix { |
| private final static Boolean is = |
| (! Windows.is() && isEnglish("LANG") && isEnglish("LC_ALL")); |
| |
| private static boolean isEnglish(String envvar) { |
| String val = getenv(envvar); |
| return (val == null) || val.matches("en.*"); |
| } |
| |
| /** Returns true if we can expect English OS error strings */ |
| static boolean is() { return is; } |
| } |
| |
| static class DelegatingProcess extends Process { |
| final Process p; |
| |
| DelegatingProcess(Process p) { |
| this.p = p; |
| } |
| |
| @Override |
| public void destroy() { |
| p.destroy(); |
| } |
| |
| @Override |
| public int exitValue() { |
| return p.exitValue(); |
| } |
| |
| @Override |
| public int waitFor() throws InterruptedException { |
| return p.waitFor(); |
| } |
| |
| @Override |
| public OutputStream getOutputStream() { |
| return p.getOutputStream(); |
| } |
| |
| @Override |
| public InputStream getInputStream() { |
| return p.getInputStream(); |
| } |
| |
| @Override |
| public InputStream getErrorStream() { |
| return p.getErrorStream(); |
| } |
| } |
| |
| private static boolean matches(String str, String regex) { |
| return Pattern.compile(regex).matcher(str).find(); |
| } |
| |
| private static String matchAndExtract(String str, String regex) { |
| Matcher matcher = Pattern.compile(regex).matcher(str); |
| if (matcher.find()) { |
| return matcher.group(); |
| } else { |
| return ""; |
| } |
| } |
| |
| /* Only used for Mac OS X -- |
| * Mac OS X (may) add the variable __CF_USER_TEXT_ENCODING to an empty |
| * environment. The environment variable JAVA_MAIN_CLASS_<pid> may also |
| * be set in Mac OS X. |
| * Remove them both from the list of env variables |
| */ |
| private static String removeMacExpectedVars(String vars) { |
| // Check for __CF_USER_TEXT_ENCODING |
| String cleanedVars = vars.replace("__CF_USER_TEXT_ENCODING=" |
| +cfUserTextEncoding+",",""); |
| // Check for JAVA_MAIN_CLASS_<pid> |
| String javaMainClassStr |
| = matchAndExtract(cleanedVars, |
| "JAVA_MAIN_CLASS_\\d+=Basic.JavaChild,"); |
| return cleanedVars.replace(javaMainClassStr,""); |
| } |
| |
| private static String sortByLinesWindowsly(String text) { |
| String[] lines = text.split("\n"); |
| Arrays.sort(lines, new WindowsComparator()); |
| StringBuilder sb = new StringBuilder(); |
| for (String line : lines) |
| sb.append(line).append("\n"); |
| return sb.toString(); |
| } |
| |
| private static void checkMapSanity(Map<String,String> map) { |
| try { |
| Set<String> keySet = map.keySet(); |
| Collection<String> values = map.values(); |
| Set<Map.Entry<String,String>> entrySet = map.entrySet(); |
| |
| equal(entrySet.size(), keySet.size()); |
| equal(entrySet.size(), values.size()); |
| |
| StringBuilder s1 = new StringBuilder(); |
| for (Map.Entry<String,String> e : entrySet) |
| s1.append(e.getKey() + "=" + e.getValue() + "\n"); |
| |
| StringBuilder s2 = new StringBuilder(); |
| for (String var : keySet) |
| s2.append(var + "=" + map.get(var) + "\n"); |
| |
| equal(s1.toString(), s2.toString()); |
| |
| Iterator<String> kIter = keySet.iterator(); |
| Iterator<String> vIter = values.iterator(); |
| Iterator<Map.Entry<String,String>> eIter = entrySet.iterator(); |
| |
| while (eIter.hasNext()) { |
| Map.Entry<String,String> entry = eIter.next(); |
| String key = kIter.next(); |
| String value = vIter.next(); |
| check(entrySet.contains(entry)); |
| check(keySet.contains(key)); |
| check(values.contains(value)); |
| check(map.containsKey(key)); |
| check(map.containsValue(value)); |
| equal(entry.getKey(), key); |
| equal(entry.getValue(), value); |
| } |
| check(! kIter.hasNext() && |
| ! vIter.hasNext()); |
| |
| } catch (Throwable t) { unexpected(t); } |
| } |
| |
| private static void checkMapEquality(Map<String,String> map1, |
| Map<String,String> map2) { |
| try { |
| equal(map1.size(), map2.size()); |
| equal(map1.isEmpty(), map2.isEmpty()); |
| for (String key : map1.keySet()) { |
| equal(map1.get(key), map2.get(key)); |
| check(map2.keySet().contains(key)); |
| } |
| equal(map1, map2); |
| equal(map2, map1); |
| equal(map1.entrySet(), map2.entrySet()); |
| equal(map2.entrySet(), map1.entrySet()); |
| equal(map1.keySet(), map2.keySet()); |
| equal(map2.keySet(), map1.keySet()); |
| |
| equal(map1.hashCode(), map2.hashCode()); |
| equal(map1.entrySet().hashCode(), map2.entrySet().hashCode()); |
| equal(map1.keySet().hashCode(), map2.keySet().hashCode()); |
| } catch (Throwable t) { unexpected(t); } |
| } |
| |
| static void checkRedirects(ProcessBuilder pb, |
| Redirect in, Redirect out, Redirect err) { |
| equal(pb.redirectInput(), in); |
| equal(pb.redirectOutput(), out); |
| equal(pb.redirectError(), err); |
| } |
| |
| static void redirectIO(ProcessBuilder pb, |
| Redirect in, Redirect out, Redirect err) { |
| pb.redirectInput(in); |
| pb.redirectOutput(out); |
| pb.redirectError(err); |
| } |
| |
| static void setFileContents(File file, String contents) { |
| try { |
| Writer w = new FileWriter(file); |
| w.write(contents); |
| w.close(); |
| } catch (Throwable t) { unexpected(t); } |
| } |
| |
| static String fileContents(File file) { |
| try { |
| Reader r = new FileReader(file); |
| StringBuilder sb = new StringBuilder(); |
| char[] buffer = new char[1024]; |
| int n; |
| while ((n = r.read(buffer)) != -1) |
| sb.append(buffer,0,n); |
| r.close(); |
| return new String(sb); |
| } catch (Throwable t) { unexpected(t); return ""; } |
| } |
| |
| static void testIORedirection() throws Throwable { |
| final File ifile = new File("ifile"); |
| final File ofile = new File("ofile"); |
| final File efile = new File("efile"); |
| ifile.delete(); |
| ofile.delete(); |
| efile.delete(); |
| |
| //---------------------------------------------------------------- |
| // Check mutual inequality of different types of Redirect |
| //---------------------------------------------------------------- |
| Redirect[] redirects = |
| { PIPE, |
| INHERIT, |
| Redirect.from(ifile), |
| Redirect.to(ifile), |
| Redirect.appendTo(ifile), |
| Redirect.from(ofile), |
| Redirect.to(ofile), |
| Redirect.appendTo(ofile), |
| }; |
| for (int i = 0; i < redirects.length; i++) |
| for (int j = 0; j < redirects.length; j++) |
| equal(redirects[i].equals(redirects[j]), (i == j)); |
| |
| //---------------------------------------------------------------- |
| // Check basic properties of different types of Redirect |
| //---------------------------------------------------------------- |
| equal(PIPE.type(), Redirect.Type.PIPE); |
| equal(PIPE.toString(), "PIPE"); |
| equal(PIPE.file(), null); |
| |
| equal(INHERIT.type(), Redirect.Type.INHERIT); |
| equal(INHERIT.toString(), "INHERIT"); |
| equal(INHERIT.file(), null); |
| |
| equal(Redirect.from(ifile).type(), Redirect.Type.READ); |
| equal(Redirect.from(ifile).toString(), |
| "redirect to read from file \"ifile\""); |
| equal(Redirect.from(ifile).file(), ifile); |
| equal(Redirect.from(ifile), |
| Redirect.from(ifile)); |
| equal(Redirect.from(ifile).hashCode(), |
| Redirect.from(ifile).hashCode()); |
| |
| equal(Redirect.to(ofile).type(), Redirect.Type.WRITE); |
| equal(Redirect.to(ofile).toString(), |
| "redirect to write to file \"ofile\""); |
| equal(Redirect.to(ofile).file(), ofile); |
| equal(Redirect.to(ofile), |
| Redirect.to(ofile)); |
| equal(Redirect.to(ofile).hashCode(), |
| Redirect.to(ofile).hashCode()); |
| |
| equal(Redirect.appendTo(ofile).type(), Redirect.Type.APPEND); |
| equal(Redirect.appendTo(efile).toString(), |
| "redirect to append to file \"efile\""); |
| equal(Redirect.appendTo(efile).file(), efile); |
| equal(Redirect.appendTo(efile), |
| Redirect.appendTo(efile)); |
| equal(Redirect.appendTo(efile).hashCode(), |
| Redirect.appendTo(efile).hashCode()); |
| |
| //---------------------------------------------------------------- |
| // Check initial values of redirects |
| //---------------------------------------------------------------- |
| List<String> childArgs = new ArrayList<String>(javaChildArgs); |
| childArgs.add("testIO"); |
| final ProcessBuilder pb = new ProcessBuilder(childArgs); |
| checkRedirects(pb, PIPE, PIPE, PIPE); |
| |
| //---------------------------------------------------------------- |
| // Check inheritIO |
| //---------------------------------------------------------------- |
| pb.inheritIO(); |
| checkRedirects(pb, INHERIT, INHERIT, INHERIT); |
| |
| //---------------------------------------------------------------- |
| // Check setters and getters agree |
| //---------------------------------------------------------------- |
| pb.redirectInput(ifile); |
| equal(pb.redirectInput().file(), ifile); |
| equal(pb.redirectInput(), Redirect.from(ifile)); |
| |
| pb.redirectOutput(ofile); |
| equal(pb.redirectOutput().file(), ofile); |
| equal(pb.redirectOutput(), Redirect.to(ofile)); |
| |
| pb.redirectError(efile); |
| equal(pb.redirectError().file(), efile); |
| equal(pb.redirectError(), Redirect.to(efile)); |
| |
| THROWS(IllegalArgumentException.class, |
| new Fun(){void f() { |
| pb.redirectInput(Redirect.to(ofile)); }}, |
| new Fun(){void f() { |
| pb.redirectInput(Redirect.appendTo(ofile)); }}, |
| new Fun(){void f() { |
| pb.redirectOutput(Redirect.from(ifile)); }}, |
| new Fun(){void f() { |
| pb.redirectError(Redirect.from(ifile)); }}); |
| |
| THROWS(IOException.class, |
| // Input file does not exist |
| new Fun(){void f() throws Throwable { pb.start(); }}); |
| setFileContents(ifile, "standard input"); |
| |
| //---------------------------------------------------------------- |
| // Writing to non-existent files |
| //---------------------------------------------------------------- |
| { |
| ProcessResults r = run(pb); |
| equal(r.exitValue(), 0); |
| equal(fileContents(ofile), "standard output"); |
| equal(fileContents(efile), "standard error"); |
| equal(r.out(), ""); |
| equal(r.err(), ""); |
| ofile.delete(); |
| efile.delete(); |
| } |
| |
| //---------------------------------------------------------------- |
| // Both redirectErrorStream + redirectError |
| //---------------------------------------------------------------- |
| { |
| pb.redirectErrorStream(true); |
| ProcessResults r = run(pb); |
| equal(r.exitValue(), 0); |
| equal(fileContents(ofile), |
| "standard error" + "standard output"); |
| equal(fileContents(efile), ""); |
| equal(r.out(), ""); |
| equal(r.err(), ""); |
| ofile.delete(); |
| efile.delete(); |
| } |
| |
| //---------------------------------------------------------------- |
| // Appending to existing files |
| //---------------------------------------------------------------- |
| { |
| setFileContents(ofile, "ofile-contents"); |
| setFileContents(efile, "efile-contents"); |
| pb.redirectOutput(Redirect.appendTo(ofile)); |
| pb.redirectError(Redirect.appendTo(efile)); |
| pb.redirectErrorStream(false); |
| ProcessResults r = run(pb); |
| equal(r.exitValue(), 0); |
| equal(fileContents(ofile), |
| "ofile-contents" + "standard output"); |
| equal(fileContents(efile), |
| "efile-contents" + "standard error"); |
| equal(r.out(), ""); |
| equal(r.err(), ""); |
| ofile.delete(); |
| efile.delete(); |
| } |
| |
| //---------------------------------------------------------------- |
| // Replacing existing files |
| //---------------------------------------------------------------- |
| { |
| setFileContents(ofile, "ofile-contents"); |
| setFileContents(efile, "efile-contents"); |
| pb.redirectOutput(ofile); |
| pb.redirectError(Redirect.to(efile)); |
| ProcessResults r = run(pb); |
| equal(r.exitValue(), 0); |
| equal(fileContents(ofile), "standard output"); |
| equal(fileContents(efile), "standard error"); |
| equal(r.out(), ""); |
| equal(r.err(), ""); |
| ofile.delete(); |
| efile.delete(); |
| } |
| |
| //---------------------------------------------------------------- |
| // Appending twice to the same file? |
| //---------------------------------------------------------------- |
| { |
| setFileContents(ofile, "ofile-contents"); |
| setFileContents(efile, "efile-contents"); |
| Redirect appender = Redirect.appendTo(ofile); |
| pb.redirectOutput(appender); |
| pb.redirectError(appender); |
| ProcessResults r = run(pb); |
| equal(r.exitValue(), 0); |
| equal(fileContents(ofile), |
| "ofile-contents" + |
| "standard error" + |
| "standard output"); |
| equal(fileContents(efile), "efile-contents"); |
| equal(r.out(), ""); |
| equal(r.err(), ""); |
| ifile.delete(); |
| ofile.delete(); |
| efile.delete(); |
| } |
| |
| //---------------------------------------------------------------- |
| // Testing INHERIT is harder. |
| // Note that this requires __FOUR__ nested JVMs involved in one test, |
| // if you count the harness JVM. |
| //---------------------------------------------------------------- |
| for (String testName : new String[] { "testInheritIO", "testRedirectInherit" } ) { |
| redirectIO(pb, PIPE, PIPE, PIPE); |
| List<String> command = pb.command(); |
| command.set(command.size() - 1, testName); |
| Process p = pb.start(); |
| new PrintStream(p.getOutputStream()).print("standard input"); |
| p.getOutputStream().close(); |
| ProcessResults r = run(p); |
| equal(r.exitValue(), 0); |
| equal(r.out(), "standard output"); |
| equal(r.err(), "standard error"); |
| } |
| |
| //---------------------------------------------------------------- |
| // Test security implications of I/O redirection |
| //---------------------------------------------------------------- |
| |
| // Read access to current directory is always granted; |
| // So create a tmpfile for input instead. |
| final File tmpFile = File.createTempFile("Basic", "tmp"); |
| setFileContents(tmpFile, "standard input"); |
| |
| final Policy policy = new Policy(); |
| Policy.setPolicy(policy); |
| System.setSecurityManager(new SecurityManager()); |
| try { |
| final Permission xPermission |
| = new FilePermission("<<ALL FILES>>", "execute"); |
| final Permission rxPermission |
| = new FilePermission("<<ALL FILES>>", "read,execute"); |
| final Permission wxPermission |
| = new FilePermission("<<ALL FILES>>", "write,execute"); |
| final Permission rwxPermission |
| = new FilePermission("<<ALL FILES>>", "read,write,execute"); |
| |
| THROWS(SecurityException.class, |
| new Fun() { void f() throws IOException { |
| policy.setPermissions(xPermission); |
| redirectIO(pb, from(tmpFile), PIPE, PIPE); |
| pb.start();}}, |
| new Fun() { void f() throws IOException { |
| policy.setPermissions(rxPermission); |
| redirectIO(pb, PIPE, to(ofile), PIPE); |
| pb.start();}}, |
| new Fun() { void f() throws IOException { |
| policy.setPermissions(rxPermission); |
| redirectIO(pb, PIPE, PIPE, to(efile)); |
| pb.start();}}); |
| |
| { |
| policy.setPermissions(rxPermission); |
| redirectIO(pb, from(tmpFile), PIPE, PIPE); |
| ProcessResults r = run(pb); |
| equal(r.out(), "standard output"); |
| equal(r.err(), "standard error"); |
| } |
| |
| { |
| policy.setPermissions(wxPermission); |
| redirectIO(pb, PIPE, to(ofile), to(efile)); |
| Process p = pb.start(); |
| new PrintStream(p.getOutputStream()).print("standard input"); |
| p.getOutputStream().close(); |
| ProcessResults r = run(p); |
| policy.setPermissions(rwxPermission); |
| equal(fileContents(ofile), "standard output"); |
| equal(fileContents(efile), "standard error"); |
| } |
| |
| { |
| policy.setPermissions(rwxPermission); |
| redirectIO(pb, from(tmpFile), to(ofile), to(efile)); |
| ProcessResults r = run(pb); |
| policy.setPermissions(rwxPermission); |
| equal(fileContents(ofile), "standard output"); |
| equal(fileContents(efile), "standard error"); |
| } |
| |
| } finally { |
| policy.setPermissions(new RuntimePermission("setSecurityManager")); |
| System.setSecurityManager(null); |
| tmpFile.delete(); |
| ifile.delete(); |
| ofile.delete(); |
| efile.delete(); |
| } |
| } |
| |
| private static void realMain(String[] args) throws Throwable { |
| if (Windows.is()) |
| System.out.println("This appears to be a Windows system."); |
| if (Unix.is()) |
| System.out.println("This appears to be a Unix system."); |
| if (UnicodeOS.is()) |
| System.out.println("This appears to be a Unicode-based OS."); |
| |
| try { testIORedirection(); } |
| catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Basic tests for setting, replacing and deleting envvars |
| //---------------------------------------------------------------- |
| try { |
| ProcessBuilder pb = new ProcessBuilder(); |
| Map<String,String> environ = pb.environment(); |
| |
| // New env var |
| environ.put("QUUX", "BAR"); |
| equal(environ.get("QUUX"), "BAR"); |
| equal(getenvInChild(pb,"QUUX"), "BAR"); |
| |
| // Modify env var |
| environ.put("QUUX","bear"); |
| equal(environ.get("QUUX"), "bear"); |
| equal(getenvInChild(pb,"QUUX"), "bear"); |
| checkMapSanity(environ); |
| |
| // Remove env var |
| environ.remove("QUUX"); |
| equal(environ.get("QUUX"), null); |
| equal(getenvInChild(pb,"QUUX"), "null"); |
| checkMapSanity(environ); |
| |
| // Remove non-existent env var |
| environ.remove("QUUX"); |
| equal(environ.get("QUUX"), null); |
| equal(getenvInChild(pb,"QUUX"), "null"); |
| checkMapSanity(environ); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Pass Empty environment to child |
| //---------------------------------------------------------------- |
| try { |
| ProcessBuilder pb = new ProcessBuilder(); |
| pb.environment().clear(); |
| String expected = Windows.is() ? "SystemRoot="+systemRoot+",": ""; |
| if (Windows.is()) { |
| pb.environment().put("SystemRoot", systemRoot); |
| } |
| String result = getenvInChild(pb); |
| if (MacOSX.is()) { |
| result = removeMacExpectedVars(result); |
| } |
| equal(result, expected); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // System.getenv() is read-only. |
| //---------------------------------------------------------------- |
| THROWS(UnsupportedOperationException.class, |
| new Fun(){void f(){ getenv().put("FOO","BAR");}}, |
| new Fun(){void f(){ getenv().remove("PATH");}}, |
| new Fun(){void f(){ getenv().keySet().remove("PATH");}}, |
| new Fun(){void f(){ getenv().values().remove("someValue");}}); |
| |
| try { |
| Collection<Map.Entry<String,String>> c = getenv().entrySet(); |
| if (! c.isEmpty()) |
| try { |
| c.iterator().next().setValue("foo"); |
| fail("Expected UnsupportedOperationException not thrown"); |
| } catch (UnsupportedOperationException e) {} // OK |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // System.getenv() always returns the same object in our implementation. |
| //---------------------------------------------------------------- |
| try { |
| check(System.getenv() == System.getenv()); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // You can't create an env var name containing "=", |
| // or an env var name or value containing NUL. |
| //---------------------------------------------------------------- |
| { |
| final Map<String,String> m = new ProcessBuilder().environment(); |
| THROWS(IllegalArgumentException.class, |
| new Fun(){void f(){ m.put("FOO=","BAR");}}, |
| new Fun(){void f(){ m.put("FOO\u0000","BAR");}}, |
| new Fun(){void f(){ m.put("FOO","BAR\u0000");}}); |
| } |
| |
| //---------------------------------------------------------------- |
| // Commands must never be null. |
| //---------------------------------------------------------------- |
| THROWS(NullPointerException.class, |
| new Fun(){void f(){ |
| new ProcessBuilder((List<String>)null);}}, |
| new Fun(){void f(){ |
| new ProcessBuilder().command((List<String>)null);}}); |
| |
| //---------------------------------------------------------------- |
| // Put in a command; get the same one back out. |
| //---------------------------------------------------------------- |
| try { |
| List<String> command = new ArrayList<String>(); |
| ProcessBuilder pb = new ProcessBuilder(command); |
| check(pb.command() == command); |
| List<String> command2 = new ArrayList<String>(2); |
| command2.add("foo"); |
| command2.add("bar"); |
| pb.command(command2); |
| check(pb.command() == command2); |
| pb.command("foo", "bar"); |
| check(pb.command() != command2 && pb.command().equals(command2)); |
| pb.command(command2); |
| command2.add("baz"); |
| equal(pb.command().get(2), "baz"); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Commands must contain at least one element. |
| //---------------------------------------------------------------- |
| THROWS(IndexOutOfBoundsException.class, |
| new Fun() { void f() throws IOException { |
| new ProcessBuilder().start();}}, |
| new Fun() { void f() throws IOException { |
| new ProcessBuilder(new ArrayList<String>()).start();}}, |
| new Fun() { void f() throws IOException { |
| Runtime.getRuntime().exec(new String[]{});}}); |
| |
| //---------------------------------------------------------------- |
| // Commands must not contain null elements at start() time. |
| //---------------------------------------------------------------- |
| THROWS(NullPointerException.class, |
| new Fun() { void f() throws IOException { |
| new ProcessBuilder("foo",null,"bar").start();}}, |
| new Fun() { void f() throws IOException { |
| new ProcessBuilder((String)null).start();}}, |
| new Fun() { void f() throws IOException { |
| new ProcessBuilder(new String[]{null}).start();}}, |
| new Fun() { void f() throws IOException { |
| new ProcessBuilder(new String[]{"foo",null,"bar"}).start();}}); |
| |
| //---------------------------------------------------------------- |
| // Command lists are growable. |
| //---------------------------------------------------------------- |
| try { |
| new ProcessBuilder().command().add("foo"); |
| new ProcessBuilder("bar").command().add("foo"); |
| new ProcessBuilder(new String[]{"1","2"}).command().add("3"); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Nulls in environment updates generate NullPointerException |
| //---------------------------------------------------------------- |
| try { |
| final Map<String,String> env = new ProcessBuilder().environment(); |
| THROWS(NullPointerException.class, |
| new Fun(){void f(){ env.put("foo",null);}}, |
| new Fun(){void f(){ env.put(null,"foo");}}, |
| new Fun(){void f(){ env.remove(null);}}, |
| new Fun(){void f(){ |
| for (Map.Entry<String,String> e : env.entrySet()) |
| e.setValue(null);}}, |
| new Fun() { void f() throws IOException { |
| Runtime.getRuntime().exec(new String[]{"foo"}, |
| new String[]{null});}}); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Non-String types in environment updates generate ClassCastException |
| //---------------------------------------------------------------- |
| try { |
| final Map<String,String> env = new ProcessBuilder().environment(); |
| THROWS(ClassCastException.class, |
| new Fun(){void f(){ env.remove(TRUE);}}, |
| new Fun(){void f(){ env.keySet().remove(TRUE);}}, |
| new Fun(){void f(){ env.values().remove(TRUE);}}, |
| new Fun(){void f(){ env.entrySet().remove(TRUE);}}); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Check query operations on environment maps |
| //---------------------------------------------------------------- |
| try { |
| List<Map<String,String>> envs = |
| new ArrayList<Map<String,String>>(2); |
| envs.add(System.getenv()); |
| envs.add(new ProcessBuilder().environment()); |
| for (final Map<String,String> env : envs) { |
| //---------------------------------------------------------------- |
| // Nulls in environment queries are forbidden. |
| //---------------------------------------------------------------- |
| THROWS(NullPointerException.class, |
| new Fun(){void f(){ getenv(null);}}, |
| new Fun(){void f(){ env.get(null);}}, |
| new Fun(){void f(){ env.containsKey(null);}}, |
| new Fun(){void f(){ env.containsValue(null);}}, |
| new Fun(){void f(){ env.keySet().contains(null);}}, |
| new Fun(){void f(){ env.values().contains(null);}}); |
| |
| //---------------------------------------------------------------- |
| // Non-String types in environment queries are forbidden. |
| //---------------------------------------------------------------- |
| THROWS(ClassCastException.class, |
| new Fun(){void f(){ env.get(TRUE);}}, |
| new Fun(){void f(){ env.containsKey(TRUE);}}, |
| new Fun(){void f(){ env.containsValue(TRUE);}}, |
| new Fun(){void f(){ env.keySet().contains(TRUE);}}, |
| new Fun(){void f(){ env.values().contains(TRUE);}}); |
| |
| //---------------------------------------------------------------- |
| // Illegal String values in environment queries are (grumble) OK |
| //---------------------------------------------------------------- |
| equal(env.get("\u0000"), null); |
| check(! env.containsKey("\u0000")); |
| check(! env.containsValue("\u0000")); |
| check(! env.keySet().contains("\u0000")); |
| check(! env.values().contains("\u0000")); |
| } |
| |
| } catch (Throwable t) { unexpected(t); } |
| |
| try { |
| final Set<Map.Entry<String,String>> entrySet = |
| new ProcessBuilder().environment().entrySet(); |
| THROWS(NullPointerException.class, |
| new Fun(){void f(){ entrySet.contains(null);}}); |
| THROWS(ClassCastException.class, |
| new Fun(){void f(){ entrySet.contains(TRUE);}}, |
| new Fun(){void f(){ |
| entrySet.contains( |
| new SimpleImmutableEntry<Boolean,String>(TRUE,""));}}); |
| |
| check(! entrySet.contains |
| (new SimpleImmutableEntry<String,String>("", ""))); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Put in a directory; get the same one back out. |
| //---------------------------------------------------------------- |
| try { |
| ProcessBuilder pb = new ProcessBuilder(); |
| File foo = new File("foo"); |
| equal(pb.directory(), null); |
| equal(pb.directory(foo).directory(), foo); |
| equal(pb.directory(null).directory(), null); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // If round-trip conversion works, check envvar pass-through to child |
| //---------------------------------------------------------------- |
| try { |
| testEncoding("ASCII", "xyzzy"); |
| testEncoding("Latin1", "\u00f1\u00e1"); |
| testEncoding("Unicode", "\u22f1\u11e1"); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // A surprisingly large number of ways to delete an environment var. |
| //---------------------------------------------------------------- |
| testVariableDeleter(new EnvironmentFrobber() { |
| public void doIt(Map<String,String> environ) { |
| environ.remove("Foo");}}); |
| |
| testVariableDeleter(new EnvironmentFrobber() { |
| public void doIt(Map<String,String> environ) { |
| environ.keySet().remove("Foo");}}); |
| |
| testVariableDeleter(new EnvironmentFrobber() { |
| public void doIt(Map<String,String> environ) { |
| environ.values().remove("BAAR");}}); |
| |
| testVariableDeleter(new EnvironmentFrobber() { |
| public void doIt(Map<String,String> environ) { |
| // Legally fabricate a ProcessEnvironment.StringEntry, |
| // even though it's private. |
| Map<String,String> environ2 |
| = new ProcessBuilder().environment(); |
| environ2.clear(); |
| environ2.put("Foo","BAAR"); |
| // Subtlety alert. |
| Map.Entry<String,String> e |
| = environ2.entrySet().iterator().next(); |
| environ.entrySet().remove(e);}}); |
| |
| testVariableDeleter(new EnvironmentFrobber() { |
| public void doIt(Map<String,String> environ) { |
| Map.Entry<String,String> victim = null; |
| for (Map.Entry<String,String> e : environ.entrySet()) |
| if (e.getKey().equals("Foo")) |
| victim = e; |
| if (victim != null) |
| environ.entrySet().remove(victim);}}); |
| |
| testVariableDeleter(new EnvironmentFrobber() { |
| public void doIt(Map<String,String> environ) { |
| Iterator<String> it = environ.keySet().iterator(); |
| while (it.hasNext()) { |
| String val = it.next(); |
| if (val.equals("Foo")) |
| it.remove();}}}); |
| |
| testVariableDeleter(new EnvironmentFrobber() { |
| public void doIt(Map<String,String> environ) { |
| Iterator<Map.Entry<String,String>> it |
| = environ.entrySet().iterator(); |
| while (it.hasNext()) { |
| Map.Entry<String,String> e = it.next(); |
| if (e.getKey().equals("Foo")) |
| it.remove();}}}); |
| |
| testVariableDeleter(new EnvironmentFrobber() { |
| public void doIt(Map<String,String> environ) { |
| Iterator<String> it = environ.values().iterator(); |
| while (it.hasNext()) { |
| String val = it.next(); |
| if (val.equals("BAAR")) |
| it.remove();}}}); |
| |
| //---------------------------------------------------------------- |
| // A surprisingly small number of ways to add an environment var. |
| //---------------------------------------------------------------- |
| testVariableAdder(new EnvironmentFrobber() { |
| public void doIt(Map<String,String> environ) { |
| environ.put("Foo","Bahrein");}}); |
| |
| //---------------------------------------------------------------- |
| // A few ways to modify an environment var. |
| //---------------------------------------------------------------- |
| testVariableModifier(new EnvironmentFrobber() { |
| public void doIt(Map<String,String> environ) { |
| environ.put("Foo","NewValue");}}); |
| |
| testVariableModifier(new EnvironmentFrobber() { |
| public void doIt(Map<String,String> environ) { |
| for (Map.Entry<String,String> e : environ.entrySet()) |
| if (e.getKey().equals("Foo")) |
| e.setValue("NewValue");}}); |
| |
| //---------------------------------------------------------------- |
| // Fiddle with environment sizes |
| //---------------------------------------------------------------- |
| try { |
| Map<String,String> environ = new ProcessBuilder().environment(); |
| int size = environ.size(); |
| checkSizes(environ, size); |
| |
| environ.put("UnLiKeLYeNVIROmtNam", "someVal"); |
| checkSizes(environ, size+1); |
| |
| // Check for environment independence |
| new ProcessBuilder().environment().clear(); |
| |
| environ.put("UnLiKeLYeNVIROmtNam", "someOtherVal"); |
| checkSizes(environ, size+1); |
| |
| environ.remove("UnLiKeLYeNVIROmtNam"); |
| checkSizes(environ, size); |
| |
| environ.clear(); |
| checkSizes(environ, 0); |
| |
| environ.clear(); |
| checkSizes(environ, 0); |
| |
| environ = new ProcessBuilder().environment(); |
| environ.keySet().clear(); |
| checkSizes(environ, 0); |
| |
| environ = new ProcessBuilder().environment(); |
| environ.entrySet().clear(); |
| checkSizes(environ, 0); |
| |
| environ = new ProcessBuilder().environment(); |
| environ.values().clear(); |
| checkSizes(environ, 0); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Check that various map invariants hold |
| //---------------------------------------------------------------- |
| checkMapSanity(new ProcessBuilder().environment()); |
| checkMapSanity(System.getenv()); |
| checkMapEquality(new ProcessBuilder().environment(), |
| new ProcessBuilder().environment()); |
| |
| |
| //---------------------------------------------------------------- |
| // Check effects on external "env" command. |
| //---------------------------------------------------------------- |
| try { |
| Set<String> env1 = new HashSet<String> |
| (Arrays.asList(nativeEnv((String[])null).split("\n"))); |
| |
| ProcessBuilder pb = new ProcessBuilder(); |
| pb.environment().put("QwErTyUiOp","AsDfGhJk"); |
| |
| Set<String> env2 = new HashSet<String> |
| (Arrays.asList(nativeEnv(pb).split("\n"))); |
| |
| check(env2.size() == env1.size() + 1); |
| env1.add("QwErTyUiOp=AsDfGhJk"); |
| check(env1.equals(env2)); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Test Runtime.exec(...envp...) |
| // Check for sort order of environment variables on Windows. |
| //---------------------------------------------------------------- |
| try { |
| String systemRoot = "SystemRoot=" + System.getenv("SystemRoot"); |
| // '+' < 'A' < 'Z' < '_' < 'a' < 'z' < '~' |
| String[]envp = {"FOO=BAR","BAZ=GORP","QUUX=", |
| "+=+", "_=_", "~=~", systemRoot}; |
| String output = nativeEnv(envp); |
| String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\n"+systemRoot+"\n_=_\n~=~\n"; |
| // On Windows, Java must keep the environment sorted. |
| // Order is random on Unix, so this test does the sort. |
| if (! Windows.is()) |
| output = sortByLinesWindowsly(output); |
| equal(output, expected); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Test Runtime.exec(...envp...) |
| // and check SystemRoot gets set automatically on Windows |
| //---------------------------------------------------------------- |
| try { |
| if (Windows.is()) { |
| String systemRoot = "SystemRoot=" + System.getenv("SystemRoot"); |
| String[]envp = {"FOO=BAR","BAZ=GORP","QUUX=", |
| "+=+", "_=_", "~=~"}; |
| String output = nativeEnv(envp); |
| String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\n"+systemRoot+"\n_=_\n~=~\n"; |
| equal(output, expected); |
| } |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // System.getenv() must be consistent with System.getenv(String) |
| //---------------------------------------------------------------- |
| try { |
| for (Map.Entry<String,String> e : getenv().entrySet()) |
| equal(getenv(e.getKey()), e.getValue()); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Fiddle with working directory in child |
| //---------------------------------------------------------------- |
| try { |
| String canonicalUserDir = |
| new File(System.getProperty("user.dir")).getCanonicalPath(); |
| String[] sdirs = new String[] |
| {".", "..", "/", "/bin", |
| "C:", "c:", "C:/", "c:\\", "\\", "\\bin", |
| "c:\\windows ", "c:\\Program Files", "c:\\Program Files\\" }; |
| for (String sdir : sdirs) { |
| File dir = new File(sdir); |
| if (! (dir.isDirectory() && dir.exists())) |
| continue; |
| out.println("Testing directory " + dir); |
| //dir = new File(dir.getCanonicalPath()); |
| |
| ProcessBuilder pb = new ProcessBuilder(); |
| equal(pb.directory(), null); |
| equal(pwdInChild(pb), canonicalUserDir); |
| |
| pb.directory(dir); |
| equal(pb.directory(), dir); |
| equal(pwdInChild(pb), dir.getCanonicalPath()); |
| |
| pb.directory(null); |
| equal(pb.directory(), null); |
| equal(pwdInChild(pb), canonicalUserDir); |
| |
| pb.directory(dir); |
| } |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Working directory with Unicode in child |
| //---------------------------------------------------------------- |
| try { |
| if (UnicodeOS.is()) { |
| File dir = new File(System.getProperty("test.dir", "."), |
| "ProcessBuilderDir\u4e00\u4e02"); |
| try { |
| if (!dir.exists()) |
| dir.mkdir(); |
| out.println("Testing Unicode directory:" + dir); |
| ProcessBuilder pb = new ProcessBuilder(); |
| pb.directory(dir); |
| equal(pwdInChild(pb), dir.getCanonicalPath()); |
| } finally { |
| if (dir.exists()) |
| dir.delete(); |
| } |
| } |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // OOME in child allocating maximally sized array |
| // Test for hotspot/jvmti bug 6850957 |
| //---------------------------------------------------------------- |
| try { |
| List<String> list = new ArrayList<String>(javaChildArgs); |
| list.add(1, String.format("-XX:OnOutOfMemoryError=%s -version", |
| javaExe)); |
| list.add("ArrayOOME"); |
| ProcessResults r = run(new ProcessBuilder(list)); |
| check(r.out().contains("java.lang.OutOfMemoryError:")); |
| check(r.out().contains(javaExe)); |
| check(r.err().contains(System.getProperty("java.version"))); |
| equal(r.exitValue(), 1); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Windows has tricky semi-case-insensitive semantics |
| //---------------------------------------------------------------- |
| if (Windows.is()) |
| try { |
| out.println("Running case insensitve variable tests"); |
| for (String[] namePair : |
| new String[][] |
| { new String[]{"PATH","PaTh"}, |
| new String[]{"home","HOME"}, |
| new String[]{"SYSTEMROOT","SystemRoot"}}) { |
| check((getenv(namePair[0]) == null && |
| getenv(namePair[1]) == null) |
| || |
| getenv(namePair[0]).equals(getenv(namePair[1])), |
| "Windows environment variables are not case insensitive"); |
| } |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Test proper Unicode child environment transfer |
| //---------------------------------------------------------------- |
| if (UnicodeOS.is()) |
| try { |
| ProcessBuilder pb = new ProcessBuilder(); |
| pb.environment().put("\u1234","\u5678"); |
| pb.environment().remove("PATH"); |
| equal(getenvInChild1234(pb), "\u5678"); |
| } catch (Throwable t) { unexpected(t); } |
| |
| |
| //---------------------------------------------------------------- |
| // Test Runtime.exec(...envp...) with envstrings with initial `=' |
| //---------------------------------------------------------------- |
| try { |
| List<String> childArgs = new ArrayList<String>(javaChildArgs); |
| childArgs.add("System.getenv()"); |
| String[] cmdp = childArgs.toArray(new String[childArgs.size()]); |
| String[] envp; |
| String[] envpWin = {"=C:=\\", "=ExitValue=3", "SystemRoot="+systemRoot}; |
| String[] envpOth = {"=ExitValue=3", "=C:=\\"}; |
| if (Windows.is()) { |
| envp = envpWin; |
| } else { |
| envp = envpOth; |
| } |
| Process p = Runtime.getRuntime().exec(cmdp, envp); |
| String expected = Windows.is() ? "=C:=\\,=ExitValue=3,SystemRoot="+systemRoot+"," : "=C:=\\,"; |
| String commandOutput = commandOutput(p); |
| if (MacOSX.is()) { |
| commandOutput = removeMacExpectedVars(commandOutput); |
| } |
| equal(commandOutput, expected); |
| if (Windows.is()) { |
| ProcessBuilder pb = new ProcessBuilder(childArgs); |
| pb.environment().clear(); |
| pb.environment().put("SystemRoot", systemRoot); |
| pb.environment().put("=ExitValue", "3"); |
| pb.environment().put("=C:", "\\"); |
| equal(commandOutput(pb), expected); |
| } |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Test Runtime.exec(...envp...) with envstrings without any `=' |
| //---------------------------------------------------------------- |
| try { |
| String[] cmdp = {"echo"}; |
| String[] envp = {"Hello", "World"}; // Yuck! |
| Process p = Runtime.getRuntime().exec(cmdp, envp); |
| equal(commandOutput(p), "\n"); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Test Runtime.exec(...envp...) with envstrings containing NULs |
| //---------------------------------------------------------------- |
| try { |
| List<String> childArgs = new ArrayList<String>(javaChildArgs); |
| childArgs.add("System.getenv()"); |
| String[] cmdp = childArgs.toArray(new String[childArgs.size()]); |
| String[] envpWin = {"SystemRoot="+systemRoot, "LC_ALL=C\u0000\u0000", // Yuck! |
| "FO\u0000=B\u0000R"}; |
| String[] envpOth = {"LC_ALL=C\u0000\u0000", // Yuck! |
| "FO\u0000=B\u0000R"}; |
| String[] envp; |
| if (Windows.is()) { |
| envp = envpWin; |
| } else { |
| envp = envpOth; |
| } |
| System.out.println ("cmdp"); |
| for (int i=0; i<cmdp.length; i++) { |
| System.out.printf ("cmdp %d: %s\n", i, cmdp[i]); |
| } |
| System.out.println ("envp"); |
| for (int i=0; i<envp.length; i++) { |
| System.out.printf ("envp %d: %s\n", i, envp[i]); |
| } |
| Process p = Runtime.getRuntime().exec(cmdp, envp); |
| String commandOutput = commandOutput(p); |
| if (MacOSX.is()) { |
| commandOutput = removeMacExpectedVars(commandOutput); |
| } |
| check(commandOutput.equals(Windows.is() |
| ? "LC_ALL=C,SystemRoot="+systemRoot+"," |
| : "LC_ALL=C,"), |
| "Incorrect handling of envstrings containing NULs"); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Test the redirectErrorStream property |
| //---------------------------------------------------------------- |
| try { |
| ProcessBuilder pb = new ProcessBuilder(); |
| equal(pb.redirectErrorStream(), false); |
| equal(pb.redirectErrorStream(true), pb); |
| equal(pb.redirectErrorStream(), true); |
| equal(pb.redirectErrorStream(false), pb); |
| equal(pb.redirectErrorStream(), false); |
| } catch (Throwable t) { unexpected(t); } |
| |
| try { |
| List<String> childArgs = new ArrayList<String>(javaChildArgs); |
| childArgs.add("OutErr"); |
| ProcessBuilder pb = new ProcessBuilder(childArgs); |
| { |
| ProcessResults r = run(pb); |
| equal(r.out(), "outout"); |
| equal(r.err(), "errerr"); |
| } |
| { |
| pb.redirectErrorStream(true); |
| ProcessResults r = run(pb); |
| equal(r.out(), "outerrouterr"); |
| equal(r.err(), ""); |
| } |
| } catch (Throwable t) { unexpected(t); } |
| |
| if (Unix.is()) { |
| //---------------------------------------------------------------- |
| // We can find true and false when PATH is null |
| //---------------------------------------------------------------- |
| try { |
| List<String> childArgs = new ArrayList<String>(javaChildArgs); |
| childArgs.add("null PATH"); |
| ProcessBuilder pb = new ProcessBuilder(childArgs); |
| pb.environment().remove("PATH"); |
| ProcessResults r = run(pb); |
| equal(r.out(), ""); |
| equal(r.err(), ""); |
| equal(r.exitValue(), 0); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // PATH search algorithm on Unix |
| //---------------------------------------------------------------- |
| try { |
| List<String> childArgs = new ArrayList<String>(javaChildArgs); |
| childArgs.add("PATH search algorithm"); |
| ProcessBuilder pb = new ProcessBuilder(childArgs); |
| pb.environment().put("PATH", "dir1:dir2:"); |
| ProcessResults r = run(pb); |
| equal(r.out(), ""); |
| equal(r.err(), ""); |
| equal(r.exitValue(), True.exitValue()); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Parent's, not child's PATH is used |
| //---------------------------------------------------------------- |
| try { |
| new File("suBdiR").mkdirs(); |
| copy("/bin/true", "suBdiR/unliKely"); |
| final ProcessBuilder pb = |
| new ProcessBuilder(new String[]{"unliKely"}); |
| pb.environment().put("PATH", "suBdiR"); |
| THROWS(IOException.class, |
| new Fun() {void f() throws Throwable {pb.start();}}); |
| } catch (Throwable t) { unexpected(t); |
| } finally { |
| new File("suBdiR/unliKely").delete(); |
| new File("suBdiR").delete(); |
| } |
| } |
| |
| //---------------------------------------------------------------- |
| // Attempt to start bogus program "" |
| //---------------------------------------------------------------- |
| try { |
| new ProcessBuilder("").start(); |
| fail("Expected IOException not thrown"); |
| } catch (IOException e) { |
| String m = e.getMessage(); |
| if (EnglishUnix.is() && |
| ! matches(m, "No such file or directory")) |
| unexpected(e); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Check that attempt to execute program name with funny |
| // characters throws an exception containing those characters. |
| //---------------------------------------------------------------- |
| for (String programName : new String[] {"\u00f0", "\u01f0"}) |
| try { |
| new ProcessBuilder(programName).start(); |
| fail("Expected IOException not thrown"); |
| } catch (IOException e) { |
| String m = e.getMessage(); |
| Pattern p = Pattern.compile(programName); |
| if (! matches(m, programName) |
| || (EnglishUnix.is() |
| && ! matches(m, "No such file or directory"))) |
| unexpected(e); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Attempt to start process in nonexistent directory fails. |
| //---------------------------------------------------------------- |
| try { |
| new ProcessBuilder("echo") |
| .directory(new File("UnLiKeLY")) |
| .start(); |
| fail("Expected IOException not thrown"); |
| } catch (IOException e) { |
| String m = e.getMessage(); |
| if (! matches(m, "in directory") |
| || (EnglishUnix.is() && |
| ! matches(m, "No such file or directory"))) |
| unexpected(e); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Attempt to write 4095 bytes to the pipe buffer without a |
| // reader to drain it would deadlock, if not for the fact that |
| // interprocess pipe buffers are at least 4096 bytes. |
| // |
| // Also, check that available reports all the bytes expected |
| // in the pipe buffer, and that I/O operations do the expected |
| // things. |
| //---------------------------------------------------------------- |
| try { |
| List<String> childArgs = new ArrayList<String>(javaChildArgs); |
| childArgs.add("print4095"); |
| final int SIZE = 4095; |
| final Process p = new ProcessBuilder(childArgs).start(); |
| print4095(p.getOutputStream(), (byte) '!'); // Might hang! |
| p.waitFor(); // Might hang! |
| equal(SIZE, p.getInputStream().available()); |
| equal(SIZE, p.getErrorStream().available()); |
| THROWS(IOException.class, |
| new Fun(){void f() throws IOException { |
| p.getOutputStream().write((byte) '!'); |
| p.getOutputStream().flush(); |
| }}); |
| |
| final byte[] bytes = new byte[SIZE + 1]; |
| equal(SIZE, p.getInputStream().read(bytes)); |
| for (int i = 0; i < SIZE; i++) |
| equal((byte) '!', bytes[i]); |
| equal((byte) 0, bytes[SIZE]); |
| |
| equal(SIZE, p.getErrorStream().read(bytes)); |
| for (int i = 0; i < SIZE; i++) |
| equal((byte) 'E', bytes[i]); |
| equal((byte) 0, bytes[SIZE]); |
| |
| equal(0, p.getInputStream().available()); |
| equal(0, p.getErrorStream().available()); |
| equal(-1, p.getErrorStream().read()); |
| equal(-1, p.getInputStream().read()); |
| |
| equal(p.exitValue(), 5); |
| |
| p.getInputStream().close(); |
| p.getErrorStream().close(); |
| try { p.getOutputStream().close(); } catch (IOException flushFailed) { } |
| |
| InputStream[] streams = { p.getInputStream(), p.getErrorStream() }; |
| for (final InputStream in : streams) { |
| Fun[] ops = { |
| new Fun(){void f() throws IOException { |
| in.read(); }}, |
| new Fun(){void f() throws IOException { |
| in.read(bytes); }}, |
| new Fun(){void f() throws IOException { |
| in.available(); }} |
| }; |
| for (Fun op : ops) { |
| try { |
| op.f(); |
| fail(); |
| } catch (IOException expected) { |
| check(expected.getMessage() |
| .matches("[Ss]tream [Cc]losed")); |
| } |
| } |
| } |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Check that reads which are pending when Process.destroy is |
| // called, get EOF, not IOException("Stream closed"). |
| //---------------------------------------------------------------- |
| try { |
| final int cases = 4; |
| for (int i = 0; i < cases; i++) { |
| final int action = i; |
| List<String> childArgs = new ArrayList<String>(javaChildArgs); |
| childArgs.add("sleep"); |
| final byte[] bytes = new byte[10]; |
| final Process p = new ProcessBuilder(childArgs).start(); |
| final CountDownLatch latch = new CountDownLatch(1); |
| final InputStream s; |
| switch (action & 0x1) { |
| case 0: s = p.getInputStream(); break; |
| case 1: s = p.getErrorStream(); break; |
| default: throw new Error(); |
| } |
| final Thread thread = new Thread() { |
| public void run() { |
| try { |
| int r; |
| latch.countDown(); |
| switch (action & 0x2) { |
| case 0: r = s.read(); break; |
| case 2: r = s.read(bytes); break; |
| default: throw new Error(); |
| } |
| equal(-1, r); |
| } catch (Throwable t) { unexpected(t); }}}; |
| |
| thread.start(); |
| latch.await(); |
| Thread.sleep(10); |
| |
| String os = System.getProperty("os.name"); |
| if (os.equalsIgnoreCase("Solaris") || |
| os.equalsIgnoreCase("SunOS")) |
| { |
| final Object deferred; |
| Class<?> c = s.getClass(); |
| if (c.getName().equals( |
| "java.lang.UNIXProcess$DeferredCloseInputStream")) |
| { |
| deferred = s; |
| } else { |
| Field deferredField = p.getClass(). |
| getDeclaredField("stdout_inner_stream"); |
| deferredField.setAccessible(true); |
| deferred = deferredField.get(p); |
| } |
| Field useCountField = deferred.getClass(). |
| getDeclaredField("useCount"); |
| useCountField.setAccessible(true); |
| |
| while (useCountField.getInt(deferred) <= 0) { |
| Thread.yield(); |
| } |
| } else if (s instanceof BufferedInputStream) { |
| Field f = Unsafe.class.getDeclaredField("theUnsafe"); |
| f.setAccessible(true); |
| Unsafe unsafe = (Unsafe)f.get(null); |
| |
| while (unsafe.tryMonitorEnter(s)) { |
| unsafe.monitorExit(s); |
| Thread.sleep(1); |
| } |
| } |
| p.destroy(); |
| thread.join(); |
| } |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Check that subprocesses which create subprocesses of their |
| // own do not cause parent to hang waiting for file |
| // descriptors to be closed. |
| //---------------------------------------------------------------- |
| try { |
| if (Unix.is() |
| && new File("/bin/bash").exists() |
| && new File("/bin/sleep").exists()) { |
| final String[] cmd = { "/bin/bash", "-c", "(/bin/sleep 6666)" }; |
| final ProcessBuilder pb = new ProcessBuilder(cmd); |
| final Process p = pb.start(); |
| final InputStream stdout = p.getInputStream(); |
| final InputStream stderr = p.getErrorStream(); |
| final OutputStream stdin = p.getOutputStream(); |
| final Thread reader = new Thread() { |
| public void run() { |
| try { stdout.read(); } |
| catch (IOException e) { |
| // Check that reader failed because stream was |
| // asynchronously closed. |
| // e.printStackTrace(); |
| if (EnglishUnix.is() && |
| ! (e.getMessage().matches(".*Bad file.*"))) |
| unexpected(e); |
| } |
| catch (Throwable t) { unexpected(t); }}}; |
| reader.setDaemon(true); |
| reader.start(); |
| Thread.sleep(100); |
| p.destroy(); |
| // Subprocess is now dead, but file descriptors remain open. |
| check(p.waitFor() != 0); |
| check(p.exitValue() != 0); |
| stdout.close(); |
| stderr.close(); |
| stdin.close(); |
| //---------------------------------------------------------- |
| // There remain unsolved issues with asynchronous close. |
| // Here's a highly non-portable experiment to demonstrate: |
| //---------------------------------------------------------- |
| if (Boolean.getBoolean("wakeupJeff!")) { |
| System.out.println("wakeupJeff!"); |
| // Initialize signal handler for INTERRUPT_SIGNAL. |
| new FileInputStream("/bin/sleep").getChannel().close(); |
| // Send INTERRUPT_SIGNAL to every thread in this java. |
| String[] wakeupJeff = { |
| "/bin/bash", "-c", |
| "/bin/ps --noheaders -Lfp $PPID | " + |
| "/usr/bin/perl -nale 'print $F[3]' | " + |
| // INTERRUPT_SIGNAL == 62 on my machine du jour. |
| "/usr/bin/xargs kill -62" |
| }; |
| new ProcessBuilder(wakeupJeff).start().waitFor(); |
| // If wakeupJeff worked, reader probably got EBADF. |
| reader.join(); |
| } |
| } |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Attempt to start process with insufficient permissions fails. |
| //---------------------------------------------------------------- |
| try { |
| new File("emptyCommand").delete(); |
| new FileOutputStream("emptyCommand").close(); |
| new File("emptyCommand").setExecutable(false); |
| new ProcessBuilder("./emptyCommand").start(); |
| fail("Expected IOException not thrown"); |
| } catch (IOException e) { |
| new File("./emptyCommand").delete(); |
| String m = e.getMessage(); |
| if (EnglishUnix.is() && |
| ! matches(m, "Permission denied")) |
| unexpected(e); |
| } catch (Throwable t) { unexpected(t); } |
| |
| new File("emptyCommand").delete(); |
| |
| //---------------------------------------------------------------- |
| // Check for correct security permission behavior |
| //---------------------------------------------------------------- |
| final Policy policy = new Policy(); |
| Policy.setPolicy(policy); |
| System.setSecurityManager(new SecurityManager()); |
| |
| try { |
| // No permissions required to CREATE a ProcessBuilder |
| policy.setPermissions(/* Nothing */); |
| new ProcessBuilder("env").directory(null).directory(); |
| new ProcessBuilder("env").directory(new File("dir")).directory(); |
| new ProcessBuilder("env").command("??").command(); |
| } catch (Throwable t) { unexpected(t); } |
| |
| THROWS(SecurityException.class, |
| new Fun() { void f() throws IOException { |
| policy.setPermissions(/* Nothing */); |
| System.getenv("foo");}}, |
| new Fun() { void f() throws IOException { |
| policy.setPermissions(/* Nothing */); |
| System.getenv();}}, |
| new Fun() { void f() throws IOException { |
| policy.setPermissions(/* Nothing */); |
| new ProcessBuilder("echo").start();}}, |
| new Fun() { void f() throws IOException { |
| policy.setPermissions(/* Nothing */); |
| Runtime.getRuntime().exec("echo");}}, |
| new Fun() { void f() throws IOException { |
| policy.setPermissions(new RuntimePermission("getenv.bar")); |
| System.getenv("foo");}}); |
| |
| try { |
| policy.setPermissions(new RuntimePermission("getenv.foo")); |
| System.getenv("foo"); |
| |
| policy.setPermissions(new RuntimePermission("getenv.*")); |
| System.getenv("foo"); |
| System.getenv(); |
| new ProcessBuilder().environment(); |
| } catch (Throwable t) { unexpected(t); } |
| |
| |
| final Permission execPermission |
| = new FilePermission("<<ALL FILES>>", "execute"); |
| |
| THROWS(SecurityException.class, |
| new Fun() { void f() throws IOException { |
| // environment permission by itself insufficient |
| policy.setPermissions(new RuntimePermission("getenv.*")); |
| ProcessBuilder pb = new ProcessBuilder("env"); |
| pb.environment().put("foo","bar"); |
| pb.start();}}, |
| new Fun() { void f() throws IOException { |
| // exec permission by itself insufficient |
| policy.setPermissions(execPermission); |
| ProcessBuilder pb = new ProcessBuilder("env"); |
| pb.environment().put("foo","bar"); |
| pb.start();}}); |
| |
| try { |
| // Both permissions? OK. |
| policy.setPermissions(new RuntimePermission("getenv.*"), |
| execPermission); |
| ProcessBuilder pb = new ProcessBuilder("env"); |
| pb.environment().put("foo","bar"); |
| Process p = pb.start(); |
| closeStreams(p); |
| } catch (IOException e) { // OK |
| } catch (Throwable t) { unexpected(t); } |
| |
| try { |
| // Don't need environment permission unless READING environment |
| policy.setPermissions(execPermission); |
| Runtime.getRuntime().exec("env", new String[]{}); |
| } catch (IOException e) { // OK |
| } catch (Throwable t) { unexpected(t); } |
| |
| try { |
| // Don't need environment permission unless READING environment |
| policy.setPermissions(execPermission); |
| new ProcessBuilder("env").start(); |
| } catch (IOException e) { // OK |
| } catch (Throwable t) { unexpected(t); } |
| |
| // Restore "normal" state without a security manager |
| policy.setPermissions(new RuntimePermission("setSecurityManager")); |
| System.setSecurityManager(null); |
| |
| //---------------------------------------------------------------- |
| // Check that Process.isAlive() & |
| // Process.waitFor(0, TimeUnit.MILLISECONDS) work as expected. |
| //---------------------------------------------------------------- |
| try { |
| List<String> childArgs = new ArrayList<String>(javaChildArgs); |
| childArgs.add("sleep"); |
| final Process p = new ProcessBuilder(childArgs).start(); |
| long start = System.nanoTime(); |
| if (!p.isAlive() || p.waitFor(0, TimeUnit.MILLISECONDS)) { |
| fail("Test failed: Process exited prematurely"); |
| } |
| long end = System.nanoTime(); |
| // give waitFor(timeout) a wide berth (100ms) |
| if ((end - start) > 100000000) |
| fail("Test failed: waitFor took too long"); |
| |
| p.destroy(); |
| p.waitFor(); |
| |
| if (p.isAlive() || |
| !p.waitFor(0, TimeUnit.MILLISECONDS)) |
| { |
| fail("Test failed: Process still alive - please terminate " + |
| p.toString() + " manually"); |
| } |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Check that Process.waitFor(timeout, TimeUnit.MILLISECONDS) |
| // works as expected. |
| //---------------------------------------------------------------- |
| try { |
| List<String> childArgs = new ArrayList<String>(javaChildArgs); |
| childArgs.add("sleep"); |
| final Process p = new ProcessBuilder(childArgs).start(); |
| long start = System.nanoTime(); |
| |
| p.waitFor(1000, TimeUnit.MILLISECONDS); |
| |
| long end = System.nanoTime(); |
| if ((end - start) < 500000000) |
| fail("Test failed: waitFor didn't take long enough"); |
| |
| p.destroy(); |
| |
| start = System.nanoTime(); |
| p.waitFor(1000, TimeUnit.MILLISECONDS); |
| end = System.nanoTime(); |
| if ((end - start) > 900000000) |
| fail("Test failed: waitFor took too long on a dead process."); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Check that Process.waitFor(timeout, TimeUnit.MILLISECONDS) |
| // interrupt works as expected. |
| //---------------------------------------------------------------- |
| try { |
| List<String> childArgs = new ArrayList<String>(javaChildArgs); |
| childArgs.add("sleep"); |
| final Process p = new ProcessBuilder(childArgs).start(); |
| final long start = System.nanoTime(); |
| final CountDownLatch latch = new CountDownLatch(1); |
| |
| final Thread thread = new Thread() { |
| public void run() { |
| try { |
| try { |
| latch.countDown(); |
| p.waitFor(10000, TimeUnit.MILLISECONDS); |
| } catch (InterruptedException e) { |
| return; |
| } |
| fail("waitFor() wasn't interrupted"); |
| } catch (Throwable t) { unexpected(t); }}}; |
| |
| thread.start(); |
| latch.await(); |
| Thread.sleep(1000); |
| thread.interrupt(); |
| p.destroy(); |
| } catch (Throwable t) { unexpected(t); } |
| |
| //---------------------------------------------------------------- |
| // Check the default implementation for |
| // Process.waitFor(long, TimeUnit) |
| //---------------------------------------------------------------- |
| try { |
| List<String> childArgs = new ArrayList<String>(javaChildArgs); |
| childArgs.add("sleep"); |
| final Process proc = new ProcessBuilder(childArgs).start(); |
| DelegatingProcess p = new DelegatingProcess(proc); |
| long start = System.nanoTime(); |
| |
| p.waitFor(1000, TimeUnit.MILLISECONDS); |
| |
| long end = System.nanoTime(); |
| if ((end - start) < 500000000) |
| fail("Test failed: waitFor didn't take long enough"); |
| |
| p.destroy(); |
| |
| p.waitFor(1000, TimeUnit.MILLISECONDS); |
| } catch (Throwable t) { unexpected(t); } |
| } |
| |
| static void closeStreams(Process p) { |
| try { |
| p.getOutputStream().close(); |
| p.getInputStream().close(); |
| p.getErrorStream().close(); |
| } catch (Throwable t) { unexpected(t); } |
| } |
| |
| //---------------------------------------------------------------- |
| // A Policy class designed to make permissions fiddling very easy. |
| //---------------------------------------------------------------- |
| private static class Policy extends java.security.Policy { |
| private Permissions perms; |
| |
| public void setPermissions(Permission...permissions) { |
| perms = new Permissions(); |
| for (Permission permission : permissions) |
| perms.add(permission); |
| } |
| |
| public Policy() { setPermissions(/* Nothing */); } |
| |
| public PermissionCollection getPermissions(CodeSource cs) { |
| return perms; |
| } |
| |
| public PermissionCollection getPermissions(ProtectionDomain pd) { |
| return perms; |
| } |
| |
| public boolean implies(ProtectionDomain pd, Permission p) { |
| return perms.implies(p); |
| } |
| |
| public void refresh() {} |
| } |
| |
| private static class StreamAccumulator extends Thread { |
| private final InputStream is; |
| private final StringBuilder sb = new StringBuilder(); |
| private Throwable throwable = null; |
| |
| public String result () throws Throwable { |
| if (throwable != null) |
| throw throwable; |
| return sb.toString(); |
| } |
| |
| StreamAccumulator (InputStream is) { |
| this.is = is; |
| } |
| |
| public void run() { |
| try { |
| Reader r = new InputStreamReader(is); |
| char[] buf = new char[4096]; |
| int n; |
| while ((n = r.read(buf)) > 0) { |
| sb.append(buf,0,n); |
| } |
| } catch (Throwable t) { |
| throwable = t; |
| } finally { |
| try { is.close(); } |
| catch (Throwable t) { throwable = t; } |
| } |
| } |
| } |
| |
| static ProcessResults run(ProcessBuilder pb) { |
| try { |
| return run(pb.start()); |
| } catch (Throwable t) { unexpected(t); return null; } |
| } |
| |
| private static ProcessResults run(Process p) { |
| Throwable throwable = null; |
| int exitValue = -1; |
| String out = ""; |
| String err = ""; |
| |
| StreamAccumulator outAccumulator = |
| new StreamAccumulator(p.getInputStream()); |
| StreamAccumulator errAccumulator = |
| new StreamAccumulator(p.getErrorStream()); |
| |
| try { |
| outAccumulator.start(); |
| errAccumulator.start(); |
| |
| exitValue = p.waitFor(); |
| |
| outAccumulator.join(); |
| errAccumulator.join(); |
| |
| out = outAccumulator.result(); |
| err = errAccumulator.result(); |
| } catch (Throwable t) { |
| throwable = t; |
| } |
| |
| return new ProcessResults(out, err, exitValue, throwable); |
| } |
| |
| //---------------------------------------------------------------- |
| // Results of a command |
| //---------------------------------------------------------------- |
| private static class ProcessResults { |
| private final String out; |
| private final String err; |
| private final int exitValue; |
| private final Throwable throwable; |
| |
| public ProcessResults(String out, |
| String err, |
| int exitValue, |
| Throwable throwable) { |
| this.out = out; |
| this.err = err; |
| this.exitValue = exitValue; |
| this.throwable = throwable; |
| } |
| |
| public String out() { return out; } |
| public String err() { return err; } |
| public int exitValue() { return exitValue; } |
| public Throwable throwable() { return throwable; } |
| |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("<STDOUT>\n" + out() + "</STDOUT>\n") |
| .append("<STDERR>\n" + err() + "</STDERR>\n") |
| .append("exitValue = " + exitValue + "\n"); |
| if (throwable != null) |
| sb.append(throwable.getStackTrace()); |
| return sb.toString(); |
| } |
| } |
| |
| //--------------------- Infrastructure --------------------------- |
| static volatile int passed = 0, failed = 0; |
| static void pass() {passed++;} |
| static void fail() {failed++; Thread.dumpStack();} |
| static void fail(String msg) {System.out.println(msg); fail();} |
| static void unexpected(Throwable t) {failed++; t.printStackTrace();} |
| static void check(boolean cond) {if (cond) pass(); else fail();} |
| static void check(boolean cond, String m) {if (cond) pass(); else fail(m);} |
| static void equal(Object x, Object y) { |
| if (x == null ? y == null : x.equals(y)) pass(); |
| else fail(x + " not equal to " + y);} |
| |
| public static void main(String[] args) throws Throwable { |
| try {realMain(args);} catch (Throwable t) {unexpected(t);} |
| System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); |
| if (failed > 0) throw new AssertionError("Some tests failed");} |
| private static abstract class Fun {abstract void f() throws Throwable;} |
| static void THROWS(Class<? extends Throwable> k, Fun... fs) { |
| for (Fun f : fs) |
| try { f.f(); fail("Expected " + k.getName() + " not thrown"); } |
| catch (Throwable t) { |
| if (k.isAssignableFrom(t.getClass())) pass(); |
| else unexpected(t);}} |
| } |