/* * Copyright 2003-2007 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.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 * @summary Basic tests for Process and Environment Variable code * @run main/othervm Basic * @author Martin Buchholz */ import java.io.*; import java.util.*; import java.security.*; import java.util.regex.Pattern; 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 { 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(); 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) throws Throwable { byte[] bytes = new byte[4095]; Arrays.fill(bytes, (byte) '!'); s.write(bytes); // Might hang! } public static class JavaChild { public static void main(String args[]) throws Throwable { String action = args[0]; 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("pwd")) { printUTF8(new File(System.getProperty("user.dir")) .getCanonicalPath()); } else if (action.equals("print4095")) { print4095(System.out); 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"); r = run(pb.start()); equal(r.exitValue(), True.exitValue()); pb.command("false"); r = run(pb.start()); equal(r.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(); 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); } // continue searching if EACCES copy("/bin/true", "dir2/prog"); equal(run(pb.start()).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.start()).exitValue(), True.exitValue()); // Check empty PATH component means current directory new File("dir1/prog").delete(); new File("dir2/prog").delete(); copy("/bin/true", "./prog"); equal(run(pb.start()).exitValue(), True.exitValue()); // 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.start()).exitValue(), True.exitValue()); copy("/bin/true", "dir3/prog"); copy("/bin/false", "dir1/prog"); equal(run(pb.start()).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 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; } } private static boolean matches(String str, String regex) { return Pattern.compile(regex).matcher(str).find(); } 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); } } 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."); //---------------------------------------------------------------- // 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(); equal(getenvInChild(pb), ""); } 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 { // '+' < 'A' < 'Z' < '_' < 'a' < 'z' < '~' String[]envp = {"FOO=BAR","BAZ=GORP","QUUX=", "+=+", "_=_", "~=~"}; String output = nativeEnv(envp); String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\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); } //---------------------------------------------------------------- // 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" }; 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.toString()); pb.directory(null); equal(pb.directory(), null); equal(pwdInChild(pb), canonicalUserDir); pb.directory(dir); } } 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 = {"=ExitValue=3", "=C:=\\"}; Process p = Runtime.getRuntime().exec(cmdp, envp); String expected = Windows.is() ? "=C:=\\,=ExitValue=3," : "=C:=\\,"; equal(commandOutput(p), expected); if (Windows.is()) { ProcessBuilder pb = new ProcessBuilder(childArgs); pb.environment().clear(); 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[] envp = {"LC_ALL=C\u0000\u0000", // Yuck! "FO\u0000=B\u0000R"}; Process p = Runtime.getRuntime().exec(cmdp, envp); check(commandOutput(p).equals("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.start()); equal(r.out(), "outout"); equal(r.err(), "errerr"); } { pb.redirectErrorStream(true); ProcessResults r = run(pb.start()); equal(r.out(), "outerrouterr"); equal(r.err(), ""); } } catch (Throwable t) { unexpected(t); } if (! Windows.is() && new File("/bin/true").exists() && new File("/bin/false").exists()) { //---------------------------------------------------------------- // 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.start()); 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.start()); 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); } //---------------------------------------------------------------- // This would deadlock, if not for the fact that // interprocess pipe buffers are at least 4096 bytes. //---------------------------------------------------------------- try { List<String> childArgs = new ArrayList<String>(javaChildArgs); childArgs.add("print4095"); Process p = new ProcessBuilder(childArgs).start(); print4095(p.getOutputStream()); // Might hang! p.waitFor(); // Might hang! equal(p.exitValue(), 5); } 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(); //e.printStackTrace(); 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"); pb.start(); } 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); } //---------------------------------------------------------------- // 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; } } } 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);}} }