1 /* 2 * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.io.File; 25 import java.io.FileReader; 26 import java.io.FileWriter; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.OutputStream; 30 import java.io.Reader; 31 import java.io.Writer; 32 import java.util.Arrays; 33 import java.util.List; 34 35 /* 36 * @test PipelineTest 37 */ 38 39 public class PipelineTest { 40 41 private static void realMain(String[] args) throws Throwable { 42 t1_simplePipeline(); 43 t2_translatePipeline(); 44 t3_redirectErrorStream(); 45 t4_failStartPipeline(); 46 } 47 48 /** 49 * Return a list of the varargs arguments. 50 * @param args elements to include in the list 51 * @param <T> the type of the elements 52 * @return a {@code List<T>} of the arguments 53 */ 54 @SafeVarargs 55 @SuppressWarnings("varargs") 56 static <T> List<T> asList(T... args) { 57 return Arrays.asList(args); 58 } 59 60 /** 61 * T1 - simple copy between two processes 62 */ 63 static void t1_simplePipeline() { 64 try { 65 String s1 = "Now is the time to check!"; 66 verify(s1, s1, 67 asList(new ProcessBuilder("cat"))); 68 verify(s1, s1, 69 asList(new ProcessBuilder("cat"), 70 new ProcessBuilder("cat"))); 71 verify(s1, s1, 72 asList(new ProcessBuilder("cat"), 73 new ProcessBuilder("cat"), 74 new ProcessBuilder("cat"))); 75 } catch (Throwable t) { 76 unexpected(t); 77 } 78 } 79 80 /** 81 * Pipeline that modifies the content. 82 */ 83 static void t2_translatePipeline() { 84 try { 85 String s2 = "Now is the time to check!"; 86 String r2 = s2.replace('e', 'E').replace('o', 'O'); 87 verify(s2, r2, 88 asList(new ProcessBuilder("tr", "e", "E"), 89 new ProcessBuilder("tr", "o", "O"))); 90 } catch (Throwable t) { 91 unexpected(t); 92 } 93 } 94 95 /** 96 * Test that redirectErrorStream sends standard error of the first process 97 * to the standard output. The standard error of the first process should be empty. 98 * The standard output of the 2nd should contain the error message including the bad file name. 99 */ 100 static void t3_redirectErrorStream() { 101 try { 102 File p1err = new File("p1-test.err"); 103 File p2out = new File("p2-test.out"); 104 105 List<Process> processes = ProcessBuilder.startPipeline( 106 asList(new ProcessBuilder("cat", "NON-EXISTENT-FILE") 107 .redirectErrorStream(true) 108 .redirectError(p1err), 109 new ProcessBuilder("cat").redirectOutput(p2out))); 110 waitForAll(processes); 111 112 check("".equals(fileContents(p1err)), "The first process standard error should be empty"); 113 String p2contents = fileContents(p2out); 114 check(p2contents.contains("NON-EXISTENT-FILE"), 115 "The error from the first process should be in the output of the second: " + p2contents); 116 } catch (Throwable t) { 117 unexpected(t); 118 } 119 } 120 121 /** 122 * Test that no processes are left after a failed startPipeline. 123 * Test illegal combinations of redirects. 124 */ 125 static void t4_failStartPipeline() { 126 File p1err = new File("p1-test.err"); 127 File p2out = new File("p2-test.out"); 128 129 THROWS(IllegalArgumentException.class, 130 () -> { 131 // Test that output redirect != PIPE throws IAE 132 List<Process> processes = ProcessBuilder.startPipeline( 133 asList(new ProcessBuilder("cat", "NON-EXISTENT-FILE1") 134 .redirectOutput(p1err), 135 new ProcessBuilder("cat"))); 136 }, 137 () -> { 138 // Test that input redirect != PIPE throws IAE 139 List<Process> processes = ProcessBuilder.startPipeline( 140 asList(new ProcessBuilder("cat", "NON-EXISTENT-FILE2"), 141 new ProcessBuilder("cat").redirectInput(p2out))); 142 } 143 ); 144 145 THROWS(NullPointerException.class, 146 () -> { 147 List<Process> processes = ProcessBuilder.startPipeline( 148 asList(new ProcessBuilder("cat", "a"), null)); 149 }, 150 () -> { 151 List<Process> processes = ProcessBuilder.startPipeline( 152 asList(null, new ProcessBuilder("cat", "b"))); 153 } 154 ); 155 156 THROWS(IOException.class, 157 () -> { 158 List<Process> processes = ProcessBuilder.startPipeline( 159 asList(new ProcessBuilder("cat", "c"), 160 new ProcessBuilder("NON-EXISTENT-COMMAND"))); 161 }); 162 163 // Check no subprocess are left behind 164 ProcessHandle.current().children().forEach(PipelineTest::print); 165 ProcessHandle.current().children() 166 .filter(p -> p.info().command().orElse("").contains("cat")) 167 .forEach(p -> fail("process should have been destroyed: " + p)); 168 } 169 170 static void verify(String input, String expected, List<ProcessBuilder> builders) throws IOException { 171 File infile = new File("test.in"); 172 File outfile = new File("test.out"); 173 setFileContents(infile, expected); 174 for (int i = 0; i < builders.size(); i++) { 175 ProcessBuilder b = builders.get(i); 176 if (i == 0) { 177 b.redirectInput(infile); 178 } 179 if (i == builders.size() - 1) { 180 b.redirectOutput(outfile); 181 } 182 } 183 List<Process> processes = ProcessBuilder.startPipeline(builders); 184 verifyProcesses(processes); 185 waitForAll(processes); 186 String result = fileContents(outfile); 187 System.out.printf(" in: %s%nout: %s%n", input, expected); 188 check(result.equals(expected), "result not as expected"); 189 } 190 191 /** 192 * Wait for each of the processes to be done. 193 * 194 * @param processes the list of processes to check 195 */ 196 static void waitForAll(List<Process> processes) { 197 processes.forEach(p -> { 198 try { 199 int status = p.waitFor(); 200 } catch (InterruptedException ie) { 201 unexpected(ie); 202 } 203 }); 204 } 205 206 static void print(ProcessBuilder pb) { 207 if (pb != null) { 208 System.out.printf(" pb: %s%n", pb); 209 System.out.printf(" cmd: %s%n", pb.command()); 210 } 211 } 212 213 static void print(ProcessHandle p) { 214 System.out.printf("process: pid: %d, info: %s%n", 215 p.pid(), p.info()); 216 } 217 218 // Check various aspects of the processes 219 static void verifyProcesses(List<Process> processes) { 220 for (int i = 0; i < processes.size(); i++) { 221 Process p = processes.get(i); 222 if (i != 0) { 223 verifyNullStream(p.getOutputStream(), "getOutputStream"); 224 } 225 if (i == processes.size() - 1) { 226 verifyNullStream(p.getInputStream(), "getInputStream"); 227 verifyNullStream(p.getErrorStream(), "getErrorStream"); 228 } 229 } 230 } 231 232 static void verifyNullStream(OutputStream s, String msg) { 233 try { 234 s.write(0xff); 235 fail("Stream should have been a NullStream" + msg); 236 } catch (IOException ie) { 237 // expected 238 } 239 } 240 241 static void verifyNullStream(InputStream s, String msg) { 242 try { 243 int len = s.read(); 244 check(len == -1, "Stream should have been a NullStream" + msg); 245 } catch (IOException ie) { 246 // expected 247 } 248 } 249 250 static void setFileContents(File file, String contents) { 251 try { 252 Writer w = new FileWriter(file); 253 w.write(contents); 254 w.close(); 255 } catch (Throwable t) { unexpected(t); } 256 } 257 258 static String fileContents(File file) { 259 try { 260 Reader r = new FileReader(file); 261 StringBuilder sb = new StringBuilder(); 262 char[] buffer = new char[1024]; 263 int n; 264 while ((n = r.read(buffer)) != -1) 265 sb.append(buffer,0,n); 266 r.close(); 267 return new String(sb); 268 } catch (Throwable t) { unexpected(t); return ""; } 269 } 270 271 //--------------------- Infrastructure --------------------------- 272 static volatile int passed = 0, failed = 0; 273 static void pass() {passed++;} 274 static void fail() {failed++; Thread.dumpStack();} 275 static void fail(String msg) {System.err.println(msg); fail();} 276 static void unexpected(Throwable t) {failed++; t.printStackTrace();} 277 static void check(boolean cond) {if (cond) pass(); else fail();} 278 static void check(boolean cond, String m) {if (cond) pass(); else fail(m);} 279 static void equal(Object x, Object y) { 280 if (x == null ? y == null : x.equals(y)) pass(); 281 else fail(">'" + x + "'<" + " not equal to " + "'" + y + "'"); 282 } 283 284 public static void main(String[] args) throws Throwable { 285 try {realMain(args);} catch (Throwable t) {unexpected(t);} 286 System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); 287 if (failed > 0) throw new AssertionError("Some tests failed"); 288 } 289 interface Fun {void f() throws Throwable;} 290 static void THROWS(Class<? extends Throwable> k, Fun... fs) { 291 for (Fun f : fs) 292 try { f.f(); fail("Expected " + k.getName() + " not thrown"); } 293 catch (Throwable t) { 294 if (k.isAssignableFrom(t.getClass())) pass(); 295 else unexpected(t);} 296 } 297 298 }