/*
* Copyright (c) 2003, 2022, 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 8003488 8054494 8058464
* 8067796 8224905 8263729 8265173 8272600 8231297 8282219 8285517
* @key intermittent
* @summary Basic tests for Process and Environment Variable code
* @modules java.base/java.lang:open
* java.base/java.io:open
* java.base/jdk.internal.misc
* @requires !vm.musl
* @library /test/lib
* @run main/othervm/native/timeout=300 -Djava.security.manager=allow Basic
* @run main/othervm/native/timeout=300 -Djava.security.manager=allow -Djdk.lang.Process.launchMechanism=fork Basic
* @author Martin Buchholz
*/
/*
* @test
* @modules java.base/java.lang:open
* java.base/java.io:open
* java.base/jdk.internal.misc
* @requires (os.family == "linux" & !vm.musl)
* @library /test/lib
* @run main/othervm/timeout=300 -Djava.security.manager=allow -Djdk.lang.Process.launchMechanism=posix_spawn Basic
*/
import java.lang.ProcessBuilder.Redirect;
import java.lang.ProcessHandle;
import static java.lang.ProcessBuilder.Redirect.*;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.security.*;
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;
import jdk.test.lib.Platform;
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");
/* used for AIX only */
static final String libpath = System.getenv(
"LIBPATH");
/* Used for regex String matching for long error messages */
static final String PERMISSION_DENIED_ERROR_MSG =
"(Permission denied|error=13)";
static final String NO_SUCH_FILE_ERROR_MSG =
"(No such file|error=2)";
/**
* Returns the number of milliseconds since time given by
* startNanoTime, which must have been previously returned from a
* call to {@link System#nanoTime()}.
*/
private static long millisElapsedSince(
long startNanoTime) {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanoTime);
}
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);
// The debug/fastdebug versions of the VM may write some warnings to stdout
// (i.e. "Warning: Cannot open log file: hotspot.log" if the VM is started
// in a directory without write permissions). These warnings will confuse tests
// which match the entire output of the child process so better filter them out.
return output.replaceAll(
"Warning:.*\\n",
"");
}
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(
"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_ERROR_MSG))
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(
"pid")) {
System.out.println(ProcessHandle.current().pid());
}
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(TrueExe.path()).exists());
check(
new File(FalseExe.path()).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_ERROR_MSG))
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_ERROR_MSG))
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(TrueExe.path(),
"dir2/prog");
equal(run(pb).exitValue(),
True.exitValue());
new File(
"dir1/prog").
delete();
new File(
"dir2/prog").
delete();
new File(
"dir2/prog").mkdirs();
copy(TrueExe.path(),
"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(TrueExe.path(),
"./prog");
equal(run(pb).exitValue(),
True.exitValue());
copy(FalseExe.path(),
"./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 #!
if (!(Platform.isLinux() && Platform.isMusl())) {
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 #!
if (!(Platform.isLinux() && Platform.isMusl())) {
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(TrueExe.path(),
"dir1/prog");
copy(FalseExe.path(),
"dir3/prog");
pb.environment().put(
"PATH",
"dir3");
equal(run(pb).exitValue(),
True.exitValue());
copy(TrueExe.path(),
"dir3/prog");
copy(FalseExe.path(),
"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)
throws IOException {
Files.copy(Paths.get(src), Paths.get(dst),
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
}
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(javaExe,
"-XX:+DisplayVMOutputToStderr",
"-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.
String jnuEncoding = System.getProperty(
"sun.jnu.encoding");
Charset cs = jnuEncoding !=
null
? Charset.forName(jnuEncoding, Charset.defaultCharset())
: Charset.defaultCharset();
if (
new String(tested.getBytes(cs), cs).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 AIX {
public static boolean is() {
return is; }
private static final boolean is =
System.getProperty(
"os.name").equals(
"AIX");
}
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; }
}
}
// On Alpine Linux, /bin/true and /bin/false are just links to /bin/busybox.
// Some tests copy /bin/true and /bin/false to files with a different filename.
// However, copying the busbox executable into a file with a different name
// won't result in the expected return codes. As workaround, we create
// executable files that can be copied and produce the expected return
// values.
private static class TrueExe {
public static String path() {
return path; }
private static final String path = path0();
private static String path0(){
if (!Platform.isBusybox(
"/bin/true")) {
return "/bin/true";
}
else {
File trueExe =
new File(
"true");
setFileContents(trueExe,
"#!/bin/true\n");
trueExe.setExecutable(
true);
return trueExe.getAbsolutePath();
}
}
}
private static class FalseExe {
public static String path() {
return path; }
private static final String path = path0();
private static String path0(){
if (!Platform.isBusybox(
"/bin/false")) {
return "/bin/false";
}
else {
File falseExe =
new File(
"false");
setFileContents(falseExe,
"#!/bin/false\n");
falseExe.setExecutable(
true);
return falseExe.getAbsolutePath();
}
}
}
static class EnglishUnix {
private static final 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.*") || val.matches(
"C");
}
/** 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,
"");
}
/* Only used for AIX --
* AIX adds the variable AIXTHREAD_GUARDPAGES=0 to the environment.
* Remove it from the list of env variables
*/
private static String removeAixExpectedVars(String vars) {
return vars.replace(
"AIXTHREAD_GUARDPAGES=0,",
"");
}
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 ""; }
}
@SuppressWarnings(
"removal")
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,
DISCARD,
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(DISCARD.type(), Redirect.Type.WRITE);
equal(DISCARD.toString(),
"WRITE");
equal(DISCARD.file(),
new File((Windows.is() ?
"NUL" :
"/dev/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 DISCARD for stdout,stderr
//----------------------------------------------------------------
redirectIO(pb, INHERIT, DISCARD, DISCARD);
checkRedirects(pb, INHERIT, DISCARD, DISCARD);
//----------------------------------------------------------------
// 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,
() -> pb.redirectInput(Redirect.to(ofile)),
() -> pb.redirectOutput(Redirect.from(ifile)),
() -> pb.redirectError(Redirect.from(ifile)),
() -> pb.redirectInput(DISCARD));
THROWS(NullPointerException.
class,
() -> pb.redirectInput((File)
null),
() -> pb.redirectOutput((File)
null),
() -> pb.redirectError((File)
null),
() -> pb.redirectInput((Redirect)
null),
() -> pb.redirectOutput((Redirect)
null),
() -> pb.redirectError((Redirect)
null));
THROWS(IOException.
class,
// Input file does not exist
() -> 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();
}
//----------------------------------------------------------------
// DISCARDing output
//----------------------------------------------------------------
{
setFileContents(ifile,
"standard input");
pb.redirectOutput(DISCARD);
pb.redirectError(DISCARD);
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(r.out(),
"");
equal(r.err(),
"");
}
//----------------------------------------------------------------
// DISCARDing output and redirecting error
//----------------------------------------------------------------
{
setFileContents(ifile,
"standard input");
setFileContents(ofile,
"ofile-contents");
setFileContents(efile,
"efile-contents");
pb.redirectOutput(DISCARD);
pb.redirectError(efile);
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(fileContents(ofile),
"ofile-contents");
equal(fileContents(efile),
"standard error");
equal(r.out(),
"");
equal(r.err(),
"");
ofile.
delete();
efile.
delete();
}
//----------------------------------------------------------------
// DISCARDing error and redirecting output
//----------------------------------------------------------------
{
setFileContents(ifile,
"standard input");
setFileContents(ofile,
"ofile-contents");
setFileContents(efile,
"efile-contents");
pb.redirectOutput(ofile);
pb.redirectError(DISCARD);
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(fileContents(ofile),
"standard output");
equal(fileContents(efile),
"efile-contents");
equal(r.out(),
"");
equal(r.err(),
"");
ofile.
delete();
efile.
delete();
}
//----------------------------------------------------------------
// DISCARDing output and merging error into output
//----------------------------------------------------------------
{
setFileContents(ifile,
"standard input");
setFileContents(ofile,
"ofile-contents");
setFileContents(efile,
"efile-contents");
pb.redirectOutput(DISCARD);
pb.redirectErrorStream(
true);
pb.redirectError(efile);
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(fileContents(ofile),
"ofile-contents");
// untouched
equal(fileContents(efile),
"");
// empty
equal(r.out(),
"");
equal(r.err(),
"");
ifile.
delete();
ofile.
delete();
efile.
delete();
pb.redirectErrorStream(
false);
// reset for next test
}
//----------------------------------------------------------------
// 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(
"<>",
"execute");
final Permission rxPermission
=
new FilePermission(
"<>",
"read,execute");
final Permission wxPermission
=
new FilePermission(
"<>",
"write,execute");
final Permission rwxPermission
=
new FilePermission(
"<>",
"read,write,execute");
THROWS(SecurityException.
class,
() -> { policy.setPermissions(xPermission);
redirectIO(pb, from(tmpFile), PIPE, PIPE);
pb.start();},
() -> { policy.setPermissions(rxPermission);
redirectIO(pb, PIPE, to(ofile), PIPE);
pb.start();},
() -> { 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();
}
}
static void checkProcessPid() {
ProcessBuilder pb =
new ProcessBuilder();
List<String> list =
new ArrayList<String>(javaChildArgs);
list.add(
"pid");
pb.command(list);
try {
Process p = pb.start();
String s = commandOutput(p);
long actualPid =
Long.valueOf(s.trim());
long expectedPid = p.pid();
equal(actualPid, expectedPid);
}
catch (Throwable t) {
unexpected(t);
}
// Test the default implementation of Process.getPid
DelegatingProcess p =
new DelegatingProcess(
null);
THROWS(UnsupportedOperationException.
class,
() -> p.pid(),
() -> p.toHandle(),
() -> p.supportsNormalTermination(),
() -> p.children(),
() -> p.descendants());
}
@SuppressWarnings(
"removal")
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 getPid()
//----------------------------------------------------------------
checkProcessPid();
//----------------------------------------------------------------
// 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+
",":
"";
expected = AIX.is() ?
"LIBPATH="+libpath+
",": expected;
if (Windows.is()) {
pb.environment().put(
"SystemRoot", systemRoot);
}
if (AIX.is()) {
pb.environment().put(
"LIBPATH", libpath);
}
String result = getenvInChild(pb);
if (MacOSX.is()) {
result = removeMacExpectedVars(result);
}
if (AIX.is()) {
result = removeAixExpectedVars(result);
}
equal(result, expected);
}
catch (Throwable t) { unexpected(t); }
//----------------------------------------------------------------
// System.getenv() is read-only.
//----------------------------------------------------------------
THROWS(UnsupportedOperationException.
class,
() -> getenv().put(
"FOO",
"BAR"),
() -> getenv().remove(
"PATH"),
() -> getenv().keySet().remove(
"PATH"),
() -> 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,
() -> m.put(
"FOO=",
"BAR"),
() -> m.put(
"FOO\u0000",
"BAR"),
() -> m.put(
"FOO",
"BAR\u0000"));
}
//----------------------------------------------------------------
// Commands must never be null.
//----------------------------------------------------------------
THROWS(NullPointerException.
class,
() ->
new ProcessBuilder((List<String>)
null),
() ->
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 ProcessBuilder().start(),
() ->
new ProcessBuilder(
new ArrayList<String>()).start(),
() -> Runtime.getRuntime().exec(
new String[]{}));
//----------------------------------------------------------------
// Commands must not contain null elements at start() time.
//----------------------------------------------------------------
THROWS(NullPointerException.
class,
() ->
new ProcessBuilder(
"foo",
null,
"bar").start(),
() ->
new ProcessBuilder((String)
null).start(),
() ->
new ProcessBuilder(
new String[]{
null}).start(),
() ->
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,
() -> env.put(
"foo",
null),
() -> env.put(
null,
"foo"),
() -> env.remove(
null),
() -> {
for (Map.Entry<String,String> e : env.entrySet())
e.setValue(
null);},
() -> 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,
() -> env.remove(
TRUE),
() -> env.keySet().remove(
TRUE),
() -> env.values().remove(
TRUE),
() -> 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,
() -> getenv(
null),
() -> env.get(
null),
() -> env.containsKey(
null),
() -> env.containsValue(
null),
() -> env.keySet().contains(
null),
() -> env.values().contains(
null));
//----------------------------------------------------------------
// Non-String types in environment queries are forbidden.
//----------------------------------------------------------------
THROWS(ClassCastException.
class,
() -> env.get(
TRUE),
() -> env.containsKey(
TRUE),
() -> env.containsValue(
TRUE),
() -> env.keySet().contains(
TRUE),
() -> 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,
() -> entrySet.contains(
null));
THROWS(ClassCastException.
class,
() -> entrySet.contains(
TRUE),
() -> 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
--> --------------------
--> maximum size reached
--> --------------------