20 * or visit www.oracle.com if you need additional information or have any |
20 * or visit www.oracle.com if you need additional information or have any |
21 * questions. |
21 * questions. |
22 */ |
22 */ |
23 package jdk.jpackage.test; |
23 package jdk.jpackage.test; |
24 |
24 |
|
25 import java.io.BufferedReader; |
|
26 import java.io.ByteArrayOutputStream; |
25 import java.io.IOException; |
27 import java.io.IOException; |
26 import java.io.PrintWriter; |
28 import java.io.InputStreamReader; |
27 import java.io.StringWriter; |
29 import java.io.OutputStream; |
28 import java.nio.file.Files; |
30 import java.io.PrintStream; |
|
31 import java.io.StringReader; |
29 import java.nio.file.Path; |
32 import java.nio.file.Path; |
30 import java.util.ArrayList; |
33 import java.util.ArrayList; |
31 import java.util.Arrays; |
34 import java.util.Arrays; |
32 import java.util.Collections; |
35 import java.util.Collections; |
|
36 import java.util.HashSet; |
33 import java.util.List; |
37 import java.util.List; |
34 import java.util.regex.Matcher; |
38 import java.util.Set; |
35 import java.util.regex.Pattern; |
39 import java.util.regex.Pattern; |
36 import java.util.spi.ToolProvider; |
40 import java.util.spi.ToolProvider; |
37 import java.util.stream.Collectors; |
41 import java.util.stream.Collectors; |
38 import java.util.stream.Stream; |
42 import java.util.stream.Stream; |
|
43 import jdk.jpackage.test.Functional.ThrowingSupplier; |
39 |
44 |
40 public final class Executor extends CommandArguments<Executor> { |
45 public final class Executor extends CommandArguments<Executor> { |
41 |
46 |
42 public Executor() { |
47 public Executor() { |
43 saveOutputType = SaveOutputType.NONE; |
48 saveOutputType = new HashSet<>(Set.of(SaveOutputType.NONE)); |
44 } |
49 } |
45 |
50 |
46 public Executor setExecutable(String v) { |
51 public Executor setExecutable(String v) { |
|
52 return setExecutable(Path.of(v)); |
|
53 } |
|
54 |
|
55 public Executor setExecutable(Path v) { |
47 executable = v; |
56 executable = v; |
48 if (executable != null) { |
57 if (executable != null) { |
49 toolProvider = null; |
58 toolProvider = null; |
50 } |
59 } |
51 return this; |
60 return this; |
52 } |
61 } |
53 |
62 |
54 public Executor setToolProvider(ToolProvider v) { |
63 public Executor setToolProvider(ToolProvider v) { |
55 toolProvider = v; |
64 toolProvider = v; |
|
65 filterOutJcovOutput = true; |
56 if (toolProvider != null) { |
66 if (toolProvider != null) { |
57 executable = null; |
67 executable = null; |
58 } |
68 } |
59 return this; |
69 return this; |
60 } |
70 } |
63 directory = v; |
73 directory = v; |
64 return this; |
74 return this; |
65 } |
75 } |
66 |
76 |
67 public Executor setExecutable(JavaTool v) { |
77 public Executor setExecutable(JavaTool v) { |
68 return setExecutable(v.getPath().getAbsolutePath()); |
78 filterOutJcovOutput = true; |
69 } |
79 return setExecutable(v.getPath()); |
70 |
80 } |
|
81 |
|
82 /** |
|
83 * Configures this instance to save full output that command will produce. |
|
84 * This function is mutual exclusive with |
|
85 * saveFirstLineOfOutput() function. |
|
86 * |
|
87 * @return this |
|
88 */ |
71 public Executor saveOutput() { |
89 public Executor saveOutput() { |
72 saveOutputType = SaveOutputType.FULL; |
90 saveOutputType.remove(SaveOutputType.FIRST_LINE); |
73 return this; |
91 saveOutputType.add(SaveOutputType.FULL); |
74 } |
92 return this; |
75 |
93 } |
|
94 |
|
95 /** |
|
96 * Configures how to save output that command will produce. If |
|
97 * <code>v</code> is <code>true</code>, the function call is equivalent to |
|
98 * <code>saveOutput()</code> call. If <code>v</code> is <code>false</code>, |
|
99 * the function will result in not preserving command output. |
|
100 * |
|
101 * @return this |
|
102 */ |
|
103 public Executor saveOutput(boolean v) { |
|
104 if (v) { |
|
105 saveOutput(); |
|
106 } else { |
|
107 saveOutputType.remove(SaveOutputType.FIRST_LINE); |
|
108 saveOutputType.remove(SaveOutputType.FULL); |
|
109 } |
|
110 return this; |
|
111 } |
|
112 |
|
113 /** |
|
114 * Configures this instance to save only the first line out output that |
|
115 * command will produce. This function is mutual exclusive with |
|
116 * saveOutput() function. |
|
117 * |
|
118 * @return this |
|
119 */ |
76 public Executor saveFirstLineOfOutput() { |
120 public Executor saveFirstLineOfOutput() { |
77 saveOutputType = SaveOutputType.FIRST_LINE; |
121 saveOutputType.add(SaveOutputType.FIRST_LINE); |
78 return this; |
122 saveOutputType.remove(SaveOutputType.FULL); |
79 } |
123 return this; |
80 |
124 } |
81 public Executor dumpOtput() { |
125 |
82 saveOutputType = SaveOutputType.DUMP; |
126 /** |
|
127 * Configures this instance to dump all output that command will produce to |
|
128 * System.out and System.err. Can be used together with saveOutput() and |
|
129 * saveFirstLineOfOutput() to save command output and also copy it in the |
|
130 * default output streams. |
|
131 * |
|
132 * @return this |
|
133 */ |
|
134 public Executor dumpOutput() { |
|
135 return dumpOutput(true); |
|
136 } |
|
137 |
|
138 public Executor dumpOutput(boolean v) { |
|
139 if (v) { |
|
140 saveOutputType.add(SaveOutputType.DUMP); |
|
141 } else { |
|
142 saveOutputType.remove(SaveOutputType.DUMP); |
|
143 } |
83 return this; |
144 return this; |
84 } |
145 } |
85 |
146 |
86 public class Result { |
147 public class Result { |
87 |
148 |
115 final int exitCode; |
176 final int exitCode; |
116 private List<String> output; |
177 private List<String> output; |
117 } |
178 } |
118 |
179 |
119 public Result execute() { |
180 public Result execute() { |
120 if (toolProvider != null) { |
181 return ThrowingSupplier.toSupplier(() -> { |
121 return runToolProvider(); |
182 if (toolProvider != null) { |
122 } |
183 return runToolProvider(); |
123 |
184 } |
124 try { |
185 |
125 if (executable != null) { |
186 if (executable != null) { |
126 return runExecutable(); |
187 return runExecutable(); |
127 } |
188 } |
128 } catch (RuntimeException e) { |
189 |
129 throw e; |
190 throw new IllegalStateException("No command to execute"); |
130 } catch (IOException | InterruptedException e) { |
191 }).get(); |
131 throw new RuntimeException(e); |
|
132 } |
|
133 |
|
134 throw new IllegalStateException("No command to execute"); |
|
135 } |
192 } |
136 |
193 |
137 public String executeAndGetFirstLineOfOutput() { |
194 public String executeAndGetFirstLineOfOutput() { |
138 return saveFirstLineOfOutput().execute().assertExitCodeIsZero().getFirstLineOfOutput(); |
195 return saveFirstLineOfOutput().execute().assertExitCodeIsZero().getFirstLineOfOutput(); |
139 } |
196 } |
140 |
197 |
141 public List<String> executeAndGetOutput() { |
198 public List<String> executeAndGetOutput() { |
142 return saveOutput().execute().assertExitCodeIsZero().getOutput(); |
199 return saveOutput().execute().assertExitCodeIsZero().getOutput(); |
143 } |
200 } |
144 |
201 |
|
202 private boolean withSavedOutput() { |
|
203 return saveOutputType.contains(SaveOutputType.FULL) || saveOutputType.contains( |
|
204 SaveOutputType.FIRST_LINE); |
|
205 } |
|
206 |
145 private Result runExecutable() throws IOException, InterruptedException { |
207 private Result runExecutable() throws IOException, InterruptedException { |
146 List<String> command = new ArrayList<>(); |
208 List<String> command = new ArrayList<>(); |
147 command.add(executable); |
209 command.add(executable.toString()); |
148 command.addAll(args); |
210 command.addAll(args); |
149 Path outputFile = null; |
|
150 ProcessBuilder builder = new ProcessBuilder(command); |
211 ProcessBuilder builder = new ProcessBuilder(command); |
151 StringBuilder sb = new StringBuilder(getPrintableCommandLine()); |
212 StringBuilder sb = new StringBuilder(getPrintableCommandLine()); |
152 if (saveOutputType != SaveOutputType.NONE) { |
213 if (withSavedOutput()) { |
153 builder.redirectErrorStream(true); |
214 builder.redirectErrorStream(true); |
154 |
215 sb.append("; save output"); |
155 if (saveOutputType == SaveOutputType.DUMP) { |
216 } else if (saveOutputType.contains(SaveOutputType.DUMP)) { |
156 builder.inheritIO(); |
217 builder.inheritIO(); |
157 sb.append("; redirect output to stdout"); |
218 sb.append("; inherit I/O"); |
158 } else { |
|
159 outputFile = Test.createTempFile(".out"); |
|
160 builder.redirectOutput(outputFile.toFile()); |
|
161 sb.append(String.format("; redirect output to [%s]", outputFile)); |
|
162 } |
|
163 } |
219 } |
164 if (directory != null) { |
220 if (directory != null) { |
165 builder.directory(directory.toFile()); |
221 builder.directory(directory.toFile()); |
166 sb.append(String.format("; in directory [%s]", directory)); |
222 sb.append(String.format("; in directory [%s]", directory)); |
167 } |
223 } |
168 |
224 |
169 try { |
225 TKit.trace("Execute " + sb.toString() + "..."); |
170 Test.trace("Execute " + sb.toString() + "..."); |
226 Process process = builder.start(); |
171 Process process = builder.start(); |
227 |
172 Result reply = new Result(process.waitFor()); |
228 List<String> outputLines = null; |
173 Test.trace("Done. Exit code: " + reply.exitCode); |
229 if (withSavedOutput()) { |
174 if (saveOutputType == SaveOutputType.FIRST_LINE) { |
230 try (BufferedReader outReader = new BufferedReader( |
175 // If the command produced no ouput, save null in 'result.output' list. |
231 new InputStreamReader(process.getInputStream()))) { |
176 reply.output = Arrays.asList( |
232 if (saveOutputType.contains(SaveOutputType.DUMP) || saveOutputType.contains( |
177 Files.readAllLines(outputFile).stream().findFirst().orElse( |
233 SaveOutputType.FULL)) { |
178 null)); |
234 outputLines = outReader.lines().collect(Collectors.toList()); |
179 } else if (saveOutputType == SaveOutputType.FULL) { |
235 } else { |
180 reply.output = Collections.unmodifiableList(Files.readAllLines( |
236 outputLines = Arrays.asList( |
181 outputFile)); |
237 outReader.lines().findFirst().orElse(null)); |
|
238 } |
|
239 } finally { |
|
240 if (saveOutputType.contains(SaveOutputType.DUMP) && outputLines != null) { |
|
241 outputLines.stream().forEach(System.out::println); |
|
242 if (saveOutputType.contains(SaveOutputType.FIRST_LINE)) { |
|
243 // Pick the first line of saved output if there is one |
|
244 for (String line: outputLines) { |
|
245 outputLines = List.of(line); |
|
246 break; |
|
247 } |
|
248 } |
|
249 } |
|
250 } |
|
251 } |
|
252 |
|
253 Result reply = new Result(process.waitFor()); |
|
254 TKit.trace("Done. Exit code: " + reply.exitCode); |
|
255 |
|
256 if (outputLines != null) { |
|
257 reply.output = Collections.unmodifiableList(outputLines); |
|
258 } |
|
259 return reply; |
|
260 } |
|
261 |
|
262 private Result runToolProvider(PrintStream out, PrintStream err) { |
|
263 TKit.trace("Execute " + getPrintableCommandLine() + "..."); |
|
264 Result reply = new Result(toolProvider.run(out, err, args.toArray( |
|
265 String[]::new))); |
|
266 TKit.trace("Done. Exit code: " + reply.exitCode); |
|
267 return reply; |
|
268 } |
|
269 |
|
270 |
|
271 private Result runToolProvider() throws IOException { |
|
272 if (!withSavedOutput()) { |
|
273 if (saveOutputType.contains(SaveOutputType.DUMP)) { |
|
274 return runToolProvider(System.out, System.err); |
|
275 } |
|
276 |
|
277 PrintStream nullPrintStream = new PrintStream(new OutputStream() { |
|
278 @Override |
|
279 public void write(int b) { |
|
280 // Nop |
|
281 } |
|
282 }); |
|
283 return runToolProvider(nullPrintStream, nullPrintStream); |
|
284 } |
|
285 |
|
286 try (ByteArrayOutputStream buf = new ByteArrayOutputStream(); |
|
287 PrintStream ps = new PrintStream(buf)) { |
|
288 Result reply = runToolProvider(ps, ps); |
|
289 ps.flush(); |
|
290 try (BufferedReader bufReader = new BufferedReader(new StringReader( |
|
291 buf.toString()))) { |
|
292 if (saveOutputType.contains(SaveOutputType.FIRST_LINE)) { |
|
293 String firstLine = filterJcovOutput(bufReader.lines()).findFirst().orElse( |
|
294 null); |
|
295 if (firstLine != null) { |
|
296 reply.output = List.of(firstLine); |
|
297 } |
|
298 } else if (saveOutputType.contains(SaveOutputType.FULL)) { |
|
299 reply.output = filterJcovOutput(bufReader.lines()).collect( |
|
300 Collectors.toUnmodifiableList()); |
|
301 } |
|
302 |
|
303 if (saveOutputType.contains(SaveOutputType.DUMP)) { |
|
304 Stream<String> lines; |
|
305 if (saveOutputType.contains(SaveOutputType.FULL)) { |
|
306 lines = reply.output.stream(); |
|
307 } else { |
|
308 lines = bufReader.lines(); |
|
309 } |
|
310 lines.forEach(System.out::println); |
|
311 } |
182 } |
312 } |
183 return reply; |
313 return reply; |
184 } finally { |
314 } |
185 if (outputFile != null) { |
|
186 Files.deleteIfExists(outputFile); |
|
187 } |
|
188 } |
|
189 } |
|
190 |
|
191 private Result runToolProvider() { |
|
192 StringWriter writer = new StringWriter(); |
|
193 PrintWriter pw = new PrintWriter(writer); |
|
194 |
|
195 Test.trace("Execute " + getPrintableCommandLine() + "..."); |
|
196 Result reply = new Result(toolProvider.run(pw, pw, args.toArray( |
|
197 String[]::new))); |
|
198 Test.trace("Done. Exit code: " + reply.exitCode); |
|
199 |
|
200 List lines = List.of(writer.toString().split("\\R", -1)); |
|
201 |
|
202 if (saveOutputType == SaveOutputType.FIRST_LINE) { |
|
203 reply.output = Stream.of(lines).findFirst().get(); |
|
204 } else if (saveOutputType == SaveOutputType.FULL) { |
|
205 reply.output = Collections.unmodifiableList(lines); |
|
206 } |
|
207 return reply; |
|
208 } |
315 } |
209 |
316 |
210 public String getPrintableCommandLine() { |
317 public String getPrintableCommandLine() { |
211 final String exec; |
318 final String exec; |
212 String format = "[%s](%d)"; |
319 String format = "[%s](%d)"; |