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