11805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes/*
21805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes * Copyright (C) 2009 The Android Open Source Project
3f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes *
41805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes * Licensed under the Apache License, Version 2.0 (the "License");
51805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes * you may not use this file except in compliance with the License.
61805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes * You may obtain a copy of the License at
7f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes *
81805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes *      http://www.apache.org/licenses/LICENSE-2.0
9f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes *
101805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes * Unless required by applicable law or agreed to in writing, software
111805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes * distributed under the License is distributed on an "AS IS" BASIS,
121805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes * See the License for the specific language governing permissions and
141805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes * limitations under the License.
151805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes */
161805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes
174557728efb66c455a52b7669a8eefef7a9e54854Jesse Wilsonpackage libcore.java.lang;
181805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes
193ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thiererimport android.system.ErrnoException;
2008ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thiererimport android.system.Os;
21690d83d222053d67ec1ee5a60768ff753487f860Tobias Thiererimport java.io.ByteArrayOutputStream;
22b8ae223e5aa37d4e91fe004d5cd8ed323c5bb604Jesse Wilsonimport java.io.File;
2308ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thiererimport java.io.FileDescriptor;
24690d83d222053d67ec1ee5a60768ff753487f860Tobias Thiererimport java.io.FileWriter;
252353846b64570fa5932028143a0af507d41a85c5Jesse Wilsonimport java.io.IOException;
262353846b64570fa5932028143a0af507d41a85c5Jesse Wilsonimport java.io.InputStream;
272353846b64570fa5932028143a0af507d41a85c5Jesse Wilsonimport java.io.OutputStream;
28690d83d222053d67ec1ee5a60768ff753487f860Tobias Thiererimport java.io.Writer;
296f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thiererimport java.lang.ProcessBuilder.Redirect;
30ed288184eafd19a15ce355e4ae8aa2cb6a16027fTobias Thiererimport java.lang.ProcessBuilder.Redirect.Type;
31690d83d222053d67ec1ee5a60768ff753487f860Tobias Thiererimport java.nio.charset.Charset;
326f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thiererimport java.util.Arrays;
3309e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thiererimport java.util.Collections;
349f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilsonimport java.util.HashMap;
356f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thiererimport java.util.List;
369f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilsonimport java.util.Map;
37690d83d222053d67ec1ee5a60768ff753487f860Tobias Thiererimport java.util.concurrent.Future;
38690d83d222053d67ec1ee5a60768ff753487f860Tobias Thiererimport java.util.concurrent.FutureTask;
393282e7b33d1fec813a2c6cf180a54e66ad54c5deTobias Thiererimport java.util.regex.Matcher;
403282e7b33d1fec813a2c6cf180a54e66ad54c5deTobias Thiererimport java.util.regex.Pattern;
41fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinimport junit.framework.TestCase;
42690d83d222053d67ec1ee5a60768ff753487f860Tobias Thiererimport libcore.io.IoUtils;
436f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer
446f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thiererimport static java.lang.ProcessBuilder.Redirect.INHERIT;
456f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thiererimport static java.lang.ProcessBuilder.Redirect.PIPE;
461805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes
47fe0ee8ef8870338ad67ebfb6b62785e0cbdb325bPaul Duffinpublic class ProcessBuilderTest extends TestCase {
48690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    private static final String TAG = ProcessBuilderTest.class.getSimpleName();
492353846b64570fa5932028143a0af507d41a85c5Jesse Wilson
503ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer    /**
513ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer     * Returns the path to a command that is in /system/bin/ on Android but
523ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer     * /bin/ elsewhere.
533ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer     *
543ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer     * @param desktopPath the command path outside Android; must start with /bin/.
553ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer     */
563ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer    private static String commandPath(String desktopPath) {
573ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer        if (!desktopPath.startsWith("/bin/")) {
583ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer            throw new IllegalArgumentException(desktopPath);
593ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer        }
603ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer        String devicePath = System.getenv("ANDROID_ROOT") + desktopPath;
613ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer        return new File(devicePath).exists() ? devicePath : desktopPath;
623ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer    }
633ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer
641805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes    private static String shell() {
653ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer        return commandPath("/bin/sh");
661805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes    }
671805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes
68a5fb706fe4a6dbeaaf4cb1f8bbc2c68b0a2a3f3cElliott Hughes    private static void assertRedirectErrorStream(boolean doRedirect,
691805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes            String expectedOut, String expectedErr) throws Exception {
701805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes        ProcessBuilder pb = new ProcessBuilder(shell(), "-c", "echo out; echo err 1>&2");
711805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes        pb.redirectErrorStream(doRedirect);
72de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        checkProcessExecution(pb, ResultCodes.ZERO,
73de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer                "" /* processInput */, expectedOut, expectedErr);
741805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes    }
751805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes
761805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes    public void test_redirectErrorStream_true() throws Exception {
77a5fb706fe4a6dbeaaf4cb1f8bbc2c68b0a2a3f3cElliott Hughes        assertRedirectErrorStream(true, "out\nerr\n", "");
781805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes    }
791805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes
801805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes    public void test_redirectErrorStream_false() throws Exception {
81a5fb706fe4a6dbeaaf4cb1f8bbc2c68b0a2a3f3cElliott Hughes        assertRedirectErrorStream(false, "out\n", "err\n");
821805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes    }
839f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson
8408ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer    public void testRedirectErrorStream_outputAndErrorAreMerged() throws Exception {
8508ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer        Process process = new ProcessBuilder(shell())
8608ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer                .redirectErrorStream(true)
8708ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer                .start();
8808ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer        try {
893958f9cc72a15ad1cc04745b8f5a818138e24633Tobias Thierer            int pid = getChildProcessPid(process);
9008ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer            String path = "/proc/" + pid + "/fd/";
9108ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer            assertEquals("stdout and stderr should point to the same socket",
9208ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer                    Os.stat(path + "1").st_ino, Os.stat(path + "2").st_ino);
9308ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer        } finally {
9408ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer            process.destroy();
9508ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer        }
9608ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer    }
9708ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer
9808ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer    /**
9908ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer     * Tests that a child process can INHERIT this parent process's
10008ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer     * stdin / stdout / stderr file descriptors.
10108ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer     */
10208ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer    public void testRedirectInherit() throws Exception {
1033ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer        // We can't run shell() here because that exits when run with INHERITed
1043ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer        // file descriptors from this process; "sleep" is less picky.
10508ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer        Process process = new ProcessBuilder()
1063ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer                .command(commandPath("/bin/sleep"), "5") // in seconds
10708ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer                .redirectInput(Redirect.INHERIT)
10808ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer                .redirectOutput(Redirect.INHERIT)
10908ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer                .redirectError(Redirect.INHERIT)
11008ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer                .start();
11108ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer        try {
11208ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer            List<Long> parentInodes = Arrays.asList(
11308ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer                    Os.fstat(FileDescriptor.in).st_ino,
11408ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer                    Os.fstat(FileDescriptor.out).st_ino,
11508ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer                    Os.fstat(FileDescriptor.err).st_ino);
1163958f9cc72a15ad1cc04745b8f5a818138e24633Tobias Thierer            int childPid = getChildProcessPid(process);
11708ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer            // Get the inode numbers of the ends of the symlink chains
11808ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer            List<Long> childInodes = Arrays.asList(
11908ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer                    Os.stat("/proc/" + childPid + "/fd/0").st_ino,
12008ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer                    Os.stat("/proc/" + childPid + "/fd/1").st_ino,
12108ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer                    Os.stat("/proc/" + childPid + "/fd/2").st_ino);
12208ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer
12308ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer            assertEquals(parentInodes, childInodes);
1243ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer        } catch (ErrnoException e) {
1253ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer            // Either (a) Os.fstat on our PID, or (b) Os.stat on our child's PID, failed.
1263ad2773818481c00cbdc093c0a625a6cc61a97f9Tobias Thierer            throw new AssertionError("stat failed; child process: " + process, e);
12708ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer        } finally {
12808ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer            process.destroy();
12908ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer        }
13008ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer    }
13108ffa8100a7f6aaf9e574a44bb5b6fbea7937c53Tobias Thierer
132690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    public void testRedirectFile_input() throws Exception {
133690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        String inputFileContents = "process input for testing\n" + TAG;
134690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        File file = File.createTempFile(TAG, "in");
135690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        try (Writer writer = new FileWriter(file)) {
136690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            writer.write(inputFileContents);
137690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        }
138690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        ProcessBuilder pb = new ProcessBuilder(shell(), "-c", "cat").redirectInput(file);
139690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        checkProcessExecution(pb, ResultCodes.ZERO, /* processInput */ "",
140690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer                /* expectedOutput */ inputFileContents, /* expectedError */ "");
141690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        assertTrue(file.delete());
142690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    }
143690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
144690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    public void testRedirectFile_output() throws Exception {
145690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        File file = File.createTempFile(TAG, "out");
146690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        String processInput = TAG + "\narbitrary string for testing!";
147690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        ProcessBuilder pb = new ProcessBuilder(shell(), "-c", "cat").redirectOutput(file);
148690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        checkProcessExecution(pb, ResultCodes.ZERO, processInput,
149690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer                /* expectedOutput */ "", /* expectedError */ "");
150690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
151690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        String fileContents = new String(IoUtils.readFileAsByteArray(
152690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer                file.getAbsolutePath()));
153690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        assertEquals(processInput, fileContents);
154690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        assertTrue(file.delete());
155690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    }
156690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
157690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    public void testRedirectFile_error() throws Exception {
158690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        File file = File.createTempFile(TAG, "err");
159690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        String processInput = "";
160690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        String missingFilePath = "/test-missing-file-" + TAG;
161690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        ProcessBuilder pb = new ProcessBuilder("ls", missingFilePath).redirectError(file);
162690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        checkProcessExecution(pb, ResultCodes.NONZERO, processInput,
163690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer                /* expectedOutput */ "", /* expectedError */ "");
164690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
165690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        String fileContents = new String(IoUtils.readFileAsByteArray(file.getAbsolutePath()));
166690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        assertTrue(file.delete());
167690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        // We assume that the path of the missing file occurs in the ls stderr.
168690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        assertTrue("Unexpected output: " + fileContents,
169690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer                fileContents.contains(missingFilePath) && !fileContents.equals(missingFilePath));
170690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    }
171690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
172690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    public void testRedirectPipe_inputAndOutput() throws Exception {
173690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        //checkProcessExecution(pb, expectedResultCode, processInput, expectedOutput, expectedError)
174690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
175690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        String testString = "process input and output for testing\n" + TAG;
176690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        {
177690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            ProcessBuilder pb = new ProcessBuilder(shell(), "-c", "cat")
178690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer                    .redirectInput(PIPE)
179690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer                    .redirectOutput(PIPE);
180690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            checkProcessExecution(pb, ResultCodes.ZERO, testString, testString, "");
181690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        }
182690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
183690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        // Check again without specifying PIPE explicitly, since that is the default
184690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        {
185690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        ProcessBuilder pb = new ProcessBuilder(shell(), "-c", "cat");
186690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        checkProcessExecution(pb, ResultCodes.ZERO, testString, testString, "");
187690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        }
188690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
189690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        // Because the above test is symmetric regarding input vs. output, test
190690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        // another case where input and output are different.
191690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        {
192690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            ProcessBuilder pb = new ProcessBuilder("echo", testString);
193690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            checkProcessExecution(pb, ResultCodes.ZERO, "", testString + "\n", "");
194690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        }
195690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    }
196690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
197690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    public void testRedirectPipe_error() throws Exception {
198690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        String missingFilePath = "/test-missing-file-" + TAG;
199690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
200690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        // Can't use checkProcessExecution() because we don't want to rely on an exact error content
201690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        Process process = new ProcessBuilder("ls", missingFilePath)
202690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer                .redirectError(Redirect.PIPE).start();
203690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        process.getOutputStream().close(); // no process input
204690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        int resultCode = process.waitFor();
205690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        ResultCodes.NONZERO.assertMatches(resultCode);
206690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        assertEquals("", readAsString(process.getInputStream())); // no process output
207690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        String errorString = readAsString(process.getErrorStream());
208690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        // We assume that the path of the missing file occurs in the ls stderr.
209690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        assertTrue("Unexpected output: " + errorString,
210690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer                errorString.contains(missingFilePath) && !errorString.equals(missingFilePath));
211690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    }
212690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
2130bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer    public void testRedirect_nullStreams() throws IOException {
2140bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        Process process = new ProcessBuilder()
2150bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer                .command(shell())
2160bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer                .inheritIO()
2170bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer                .start();
2180bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        try {
2190bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer            assertNullInputStream(process.getInputStream());
2200bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer            assertNullOutputStream(process.getOutputStream());
2210bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer            assertNullInputStream(process.getErrorStream());
2220bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        } finally {
2230bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer            process.destroy();
2240bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        }
2250bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer    }
2260bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer
2270bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer    public void testRedirectErrorStream_nullStream() throws IOException {
2280bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        Process process = new ProcessBuilder()
2290bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer                .command(shell())
2300bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer                .redirectErrorStream(true)
2310bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer                .start();
2320bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        try {
2330bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer            assertNullInputStream(process.getErrorStream());
2340bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        } finally {
2350bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer            process.destroy();
2360bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        }
2370bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer    }
2380bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer
2399f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson    public void testEnvironment() throws Exception {
2409f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson        ProcessBuilder pb = new ProcessBuilder(shell(), "-c", "echo $A");
2419f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson        pb.environment().put("A", "android");
242de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        checkProcessExecution(pb, ResultCodes.ZERO, "", "android\n", "");
2439f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson    }
2449f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson
2452353846b64570fa5932028143a0af507d41a85c5Jesse Wilson    public void testDestroyClosesEverything() throws IOException {
2462353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        Process process = new ProcessBuilder(shell(), "-c", "echo out; echo err 1>&2").start();
2472353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        InputStream in = process.getInputStream();
2482353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        InputStream err = process.getErrorStream();
2492353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        OutputStream out = process.getOutputStream();
2502353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        process.destroy();
2512353846b64570fa5932028143a0af507d41a85c5Jesse Wilson
2522353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        try {
2532353846b64570fa5932028143a0af507d41a85c5Jesse Wilson            in.read();
2542353846b64570fa5932028143a0af507d41a85c5Jesse Wilson            fail();
2552353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        } catch (IOException expected) {
2562353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        }
2572353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        try {
2582353846b64570fa5932028143a0af507d41a85c5Jesse Wilson            err.read();
2592353846b64570fa5932028143a0af507d41a85c5Jesse Wilson            fail();
2602353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        } catch (IOException expected) {
2612353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        }
2622353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        try {
2632353846b64570fa5932028143a0af507d41a85c5Jesse Wilson            /*
2642353846b64570fa5932028143a0af507d41a85c5Jesse Wilson             * We test write+flush because the RI returns a wrapped stream, but
2652353846b64570fa5932028143a0af507d41a85c5Jesse Wilson             * only bothers to close the underlying stream.
2662353846b64570fa5932028143a0af507d41a85c5Jesse Wilson             */
2672353846b64570fa5932028143a0af507d41a85c5Jesse Wilson            out.write(1);
2682353846b64570fa5932028143a0af507d41a85c5Jesse Wilson            out.flush();
2692353846b64570fa5932028143a0af507d41a85c5Jesse Wilson            fail();
2702353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        } catch (IOException expected) {
2712353846b64570fa5932028143a0af507d41a85c5Jesse Wilson        }
2722353846b64570fa5932028143a0af507d41a85c5Jesse Wilson    }
2732353846b64570fa5932028143a0af507d41a85c5Jesse Wilson
2742353846b64570fa5932028143a0af507d41a85c5Jesse Wilson    public void testDestroyDoesNotLeak() throws IOException {
275a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin        Process process = new ProcessBuilder(shell(), "-c", "echo out; echo err 1>&2").start();
276a6f350c645dbb66d68cc2b03afb8f2eeaa88fbbaPaul Duffin        process.destroy();
2772353846b64570fa5932028143a0af507d41a85c5Jesse Wilson    }
2782353846b64570fa5932028143a0af507d41a85c5Jesse Wilson
2799f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson    public void testEnvironmentMapForbidsNulls() throws Exception {
2809f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson        ProcessBuilder pb = new ProcessBuilder(shell(), "-c", "echo $A");
2819f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson        Map<String, String> environment = pb.environment();
2829f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson        Map<String, String> before = new HashMap<String, String>(environment);
2839f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson        try {
2849f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson            environment.put("A", null);
2859f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson            fail();
2869f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson        } catch (NullPointerException expected) {
2879f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson        }
2889f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson        try {
2899f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson            environment.put(null, "android");
2909f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson            fail();
2919f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson        } catch (NullPointerException expected) {
2929f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson        }
293de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        try {
294de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer            environment.containsKey(null);
295de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer            fail("Attempting to check the presence of a null key should throw");
296de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        } catch (NullPointerException expected) {
297de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        }
298de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        try {
299de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer            environment.containsValue(null);
300de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer            fail("Attempting to check the presence of a null value should throw");
301de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        } catch (NullPointerException expected) {
302de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        }
3039f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson        assertEquals(before, environment);
3049f05b37e3f0a1c0d30e1a64e1d5115e87fe03444Jesse Wilson    }
3056f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer
3066f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    /**
307de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer     * Tests attempting to query the presence of a non-String key or value
308de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer     * in the environment map. Since that is a {@code Map<String, String>},
309de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer     * it's hard to imagine this ever breaking, but it's good to have a test
310de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer     * since it's called out in the documentation.
311de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer     */
312a072e981b99e7dd0ecc0be06d8c237e41179a6b4Andreas Gampe    @SuppressWarnings("CollectionIncompatibleType")
313de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer    public void testEnvironmentMapForbidsNonStringKeysAndValues() {
314de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        ProcessBuilder pb = new ProcessBuilder("echo", "Hello, world!");
315de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        Map<String, String> environment = pb.environment();
316de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        Integer nonString = Integer.valueOf(23);
317de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        try {
318de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer            environment.containsKey(nonString);
319de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer            fail("Attempting to query the presence of a non-String key should throw");
320de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        } catch (ClassCastException expected) {
321de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        }
322de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        try {
323de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer            environment.get(nonString);
324de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer            fail("Attempting to query the presence of a non-String key should throw");
325de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        } catch (ClassCastException expected) {
326de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        }
327de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        try {
328de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer            environment.containsValue(nonString);
329de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer            fail("Attempting to query the presence of a non-String value should throw");
330de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        } catch (ClassCastException expected) {
331de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        }
332de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer    }
333de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer
334de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer    /**
3356f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer     * Checks that INHERIT and PIPE tend to have different hashCodes
3366f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer     * in any particular instance of the runtime.
3376f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer     * We test this by asserting that they use the identity hashCode,
3386f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer     * which is a sufficient but not necessary condition for this.
3396f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer     * If the implementation changes to a different sufficient condition
3406f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer     * in future, this test should be updated accordingly.
3416f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer     */
3426f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    public void testRedirect_inheritAndPipeTendToHaveDifferentHashCode() {
3436f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertIdentityHashCode(INHERIT);
3446f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertIdentityHashCode(PIPE);
3456f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    }
3466f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer
3476f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    public void testRedirect_hashCodeDependsOnFile() {
3486f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        File file = new File("/tmp/file");
3496f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        File otherFile = new File("/tmp/some_other_file") {
3506f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer            @Override public int hashCode() { return 1 + file.hashCode(); }
3516f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        };
3526f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        Redirect a = Redirect.from(file);
3536f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        Redirect b = Redirect.from(otherFile);
3546f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertFalse("Unexpectedly equal hashCode: " + a + " vs. " + b,
3556f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                a.hashCode() == b.hashCode());
3566f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    }
3576f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer
3586f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    /**
3596f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer     * Tests that {@link Redirect}'s equals() and hashCode() is sane.
3606f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer     */
3616f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    public void testRedirect_equals() {
3626f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        File fileA = new File("/tmp/fileA");
3636f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        File fileB = new File("/tmp/fileB");
3646f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        File fileB2 = new File("/tmp/fileB");
3656f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        // check that test is set up correctly
3666f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertFalse(fileA.equals(fileB));
3676f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertEquals(fileB, fileB2);
3686f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer
3696f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertSymmetricEquals(Redirect.appendTo(fileB), Redirect.appendTo(fileB2));
3706f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertSymmetricEquals(Redirect.from(fileB), Redirect.from(fileB2));
3716f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertSymmetricEquals(Redirect.to(fileB), Redirect.to(fileB2));
3726f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer
3736f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        Redirect[] redirects = new Redirect[] {
3746f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                INHERIT,
3756f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                PIPE,
3766f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                Redirect.appendTo(fileA),
3776f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                Redirect.from(fileA),
3786f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                Redirect.to(fileA),
3796f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                Redirect.appendTo(fileB),
3806f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                Redirect.from(fileB),
3816f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                Redirect.to(fileB),
3826f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        };
3836f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        for (Redirect a : redirects) {
3846f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer            for (Redirect b : redirects) {
3856f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                if (a != b) {
3866f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                    assertFalse("Unexpectedly equal: " + a + " vs. " + b, a.equals(b));
3876f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                    assertFalse("Unexpected asymmetric equality: " + a + " vs. " + b, b.equals(a));
3886f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                }
3896f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer            }
3906f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        }
3916f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    }
3926f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer
3936f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    /**
394ed288184eafd19a15ce355e4ae8aa2cb6a16027fTobias Thierer     * Tests the {@link Redirect#type() type} and {@link Redirect#file() file} of
395ed288184eafd19a15ce355e4ae8aa2cb6a16027fTobias Thierer     * various Redirects. These guarantees are made in the respective javadocs,
396ed288184eafd19a15ce355e4ae8aa2cb6a16027fTobias Thierer     * so we're testing them together here.
3976f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer     */
398ed288184eafd19a15ce355e4ae8aa2cb6a16027fTobias Thierer    public void testRedirect_fileAndType() {
3996f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        File file = new File("/tmp/fake-file-for/java.lang.ProcessBuilderTest");
400ed288184eafd19a15ce355e4ae8aa2cb6a16027fTobias Thierer        assertRedirectFileAndType(null, Type.INHERIT, INHERIT);
401ed288184eafd19a15ce355e4ae8aa2cb6a16027fTobias Thierer        assertRedirectFileAndType(null, Type.PIPE, PIPE);
402ed288184eafd19a15ce355e4ae8aa2cb6a16027fTobias Thierer        assertRedirectFileAndType(file, Type.APPEND, Redirect.appendTo(file));
403ed288184eafd19a15ce355e4ae8aa2cb6a16027fTobias Thierer        assertRedirectFileAndType(file, Type.READ, Redirect.from(file));
404ed288184eafd19a15ce355e4ae8aa2cb6a16027fTobias Thierer        assertRedirectFileAndType(file, Type.WRITE, Redirect.to(file));
4056f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    }
4066f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer
407ed288184eafd19a15ce355e4ae8aa2cb6a16027fTobias Thierer    private static void assertRedirectFileAndType(File expectedFile, Type expectedType,
408ed288184eafd19a15ce355e4ae8aa2cb6a16027fTobias Thierer            Redirect redirect) {
409ed288184eafd19a15ce355e4ae8aa2cb6a16027fTobias Thierer        assertEquals(redirect.toString(), expectedFile, redirect.file());
4106f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertEquals(redirect.toString(), expectedType, redirect.type());
4116f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    }
4126f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer
413690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    public void testRedirect_defaultsToPipe() {
41409e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer        assertRedirects(PIPE, PIPE, PIPE, new ProcessBuilder());
415690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    }
416690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
4176f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    public void testRedirect_setAndGet() {
4186f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        File file = new File("/tmp/fake-file-for/java.lang.ProcessBuilderTest");
41909e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer        assertRedirects(Redirect.from(file), PIPE, PIPE, new ProcessBuilder().redirectInput(file));
42009e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer        assertRedirects(PIPE, Redirect.to(file), PIPE, new ProcessBuilder().redirectOutput(file));
42109e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer        assertRedirects(PIPE, PIPE, Redirect.to(file), new ProcessBuilder().redirectError(file));
42209e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer        assertRedirects(Redirect.from(file), INHERIT, Redirect.to(file),
4236f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                new ProcessBuilder()
4246f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                        .redirectInput(PIPE)
4256f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                        .redirectOutput(INHERIT)
4266f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                        .redirectError(file)
4276f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                        .redirectInput(file));
42809e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer
42909e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer        assertRedirects(Redirect.INHERIT, Redirect.INHERIT, Redirect.INHERIT,
43009e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer                new ProcessBuilder().inheritIO());
43109e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer    }
43209e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer
43309e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer    public void testCommand_setAndGet() {
43409e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer        List<String> expected = Collections.unmodifiableList(
43509e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer                Arrays.asList("echo", "fake", "command", "for", TAG));
43609e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer        assertEquals(expected, new ProcessBuilder().command(expected).command());
43709e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer        assertEquals(expected, new ProcessBuilder().command("echo", "fake", "command", "for", TAG)
43809e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer                .command());
43909e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer    }
44009e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer
44109e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer    public void testDirectory_setAndGet() {
44209e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer        File directory = new File("/tmp/fake/directory/for/" + TAG);
44309e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer        assertEquals(directory, new ProcessBuilder().directory(directory).directory());
444de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        assertNull(new ProcessBuilder().directory());
445de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        assertNull(new ProcessBuilder()
446de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer                .directory(directory)
447de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer                .directory(null)
448de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer                .directory());
4496f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    }
4506f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer
451690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    /**
452690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer     * One or more result codes returned by {@link Process#waitFor()}.
453690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer     */
454690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    enum ResultCodes {
455690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        ZERO { @Override void assertMatches(int actualResultCode) {
456690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            assertEquals(0, actualResultCode);
457690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        } },
458690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        NONZERO { @Override void assertMatches(int actualResultCode) {
459690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            assertTrue("Expected resultCode != 0, got 0", actualResultCode != 0);
460690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        } };
461690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
462690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        /** asserts that the given code falls within this ResultCodes */
463690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        abstract void assertMatches(int actualResultCode);
464690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    }
465690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
466690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    /**
467690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer     * Starts the specified process, writes the specified input to it and waits for the process
468690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer     * to finish; then, then checks that the result code and output / error are expected.
469690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer     *
470690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer     * <p>This method assumes that the process consumes and produces character data encoded with
471690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer     * the platform default charset.
472690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer     */
473690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    private static void checkProcessExecution(ProcessBuilder pb,
474690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            ResultCodes expectedResultCode, String processInput,
475690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            String expectedOutput, String expectedError) throws Exception {
476690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        Process process = pb.start();
477690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        Future<String> processOutput = asyncRead(process.getInputStream());
478690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        Future<String> processError = asyncRead(process.getErrorStream());
479690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        try (OutputStream outputStream = process.getOutputStream()) {
480690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            outputStream.write(processInput.getBytes(Charset.defaultCharset()));
481690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        }
482690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        int actualResultCode = process.waitFor();
483690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        expectedResultCode.assertMatches(actualResultCode);
484690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        assertEquals(expectedOutput, processOutput.get());
485690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        assertEquals(expectedError, processError.get());
486690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    }
487690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
4880bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer    /**
4890bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer     * Asserts that inputStream is a <a href="ProcessBuilder#redirect-input">null input stream</a>.
4900bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer     */
4910bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer    private static void assertNullInputStream(InputStream inputStream) throws IOException {
4920bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        assertEquals(-1, inputStream.read());
4930bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        assertEquals(0, inputStream.available());
4940bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        inputStream.close(); // should do nothing
4950bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer    }
4960bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer
4970bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer    /**
4980bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer     * Asserts that outputStream is a <a href="ProcessBuilder#redirect-output">null output
4990bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer     * stream</a>.
5000bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer     */
5010bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer    private static void assertNullOutputStream(OutputStream outputStream) throws IOException {
5020bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        try {
5030bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer            outputStream.write(42);
5040bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer            fail("NullOutputStream.write(int) must throw IOException: " + outputStream);
5050bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        } catch (IOException expected) {
5060bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer            // expected
5070bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        }
5080bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer        outputStream.close(); // should do nothing
5090bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer    }
5100bb0ab08422f1f91152aaa9a171f3afefbc0ca50Tobias Thierer
51109e6f66ef070a71a99dd27c9c5c05ea02d903f63Tobias Thierer    private static void assertRedirects(Redirect in, Redirect out, Redirect err, ProcessBuilder pb) {
5126f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        List<Redirect> expected = Arrays.asList(in, out, err);
5136f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        List<Redirect> actual = Arrays.asList(
5146f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer                pb.redirectInput(), pb.redirectOutput(), pb.redirectError());
5156f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertEquals(expected, actual);
5166f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    }
5176f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer
5186f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    private static void assertIdentityHashCode(Redirect redirect) {
5196f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertEquals(System.identityHashCode(redirect), redirect.hashCode());
5206f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    }
5216f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer
5226f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    private static void assertSymmetricEquals(Redirect a, Redirect b) {
5236f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertEquals(a, b);
5246f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertEquals(b, a);
5256f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer        assertEquals(a.hashCode(), b.hashCode());
5266f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer    }
5276f4fb5eb1980955c1ede2c25036613f3977ada1fTobias Thierer
5283958f9cc72a15ad1cc04745b8f5a818138e24633Tobias Thierer    private static int getChildProcessPid(Process process) {
529de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        // Hack: UNIXProcess.pid is private; parse toString() instead of reflection
530de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        Matcher matcher = Pattern.compile("pid=(\\d+)").matcher(process.toString());
531de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        assertTrue("Can't find PID in: " + process, matcher.find());
5323958f9cc72a15ad1cc04745b8f5a818138e24633Tobias Thierer        int result = Integer.parseInt(matcher.group(1));
533de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer        return result;
534de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer    }
535de6cabdeee1c4e0efbefdb2febb8b71d945b5f07Tobias Thierer
536690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    static String readAsString(InputStream inputStream) throws IOException {
537690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
538690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        byte[] data = new byte[1024];
539690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        int numRead;
540690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        while ((numRead = inputStream.read(data)) >= 0) {
541690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            outputStream.write(data, 0, numRead);
542690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        }
543690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        return new String(outputStream.toByteArray(), Charset.defaultCharset());
544690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    }
545690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
546690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    /**
547690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer     * Reads the entire specified {@code inputStream} asynchronously.
548690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer     */
549690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    static FutureTask<String> asyncRead(final InputStream inputStream) {
550690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        final FutureTask<String> result = new FutureTask<>(() -> readAsString(inputStream));
551690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        new Thread("read asynchronously from " + inputStream) {
552690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            @Override
553690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            public void run() {
554690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer                result.run();
555690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer            }
556690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        }.start();
557690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer        return result;
558690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer    }
559690d83d222053d67ec1ee5a60768ff753487f860Tobias Thierer
5601805727c24b2b80161fef93c4b7742cf2322bdeaElliott Hughes}
561