Starts a process from its builder.
+ * )null);
+ }
+
+ /**
+ * Starts a process from its builder.
+ * The default redirects of STDOUT and STDERR are started
+ * It is possible to monitor the in-streams via the provided {@code consumer}
+ * @param name The process name
+ * @param consumer {@linkplain Consumer} instance to process the in-streams
+ * @param processBuilder The process builder
+ * @return Returns the initialized process
+ * @throws IOException
+ */
+ @SuppressWarnings("overloads")
+ public static Process startProcess(String name,
+ ProcessBuilder processBuilder,
+ Consumer consumer)
+ throws IOException {
+ try {
+ return startProcess(name, processBuilder, consumer, null, -1, TimeUnit.NANOSECONDS);
+ } catch (InterruptedException | TimeoutException e) {
+ // will never happen
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Starts a process from its builder.
+ * The default redirects of STDOUT and STDERR are started
+ *
+ * It is possible to wait for the process to get to a warmed-up state
+ * via {@linkplain Predicate} condition on the STDOUT
+ *
+ * @param name The process name
+ * @param processBuilder The process builder
+ * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
+ * Used to determine the moment the target app is
+ * properly warmed-up.
+ * It can be null - in that case the warmup is skipped.
+ * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever
+ * @param unit The timeout {@linkplain TimeUnit}
+ * @return Returns the initialized {@linkplain Process}
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws TimeoutException
+ */
+ public static Process startProcess(String name,
+ ProcessBuilder processBuilder,
+ final Predicate linePredicate,
+ long timeout,
+ TimeUnit unit)
+ throws IOException, InterruptedException, TimeoutException {
+ return startProcess(name, processBuilder, null, linePredicate, timeout, unit);
+ }
+
+ /**
+ * Starts a process from its builder.
+ * The default redirects of STDOUT and STDERR are started
+ *
+ * It is possible to wait for the process to get to a warmed-up state
+ * via {@linkplain Predicate} condition on the STDOUT and monitor the
+ * in-streams via the provided {@linkplain Consumer}
+ *
+ * @param name The process name
+ * @param processBuilder The process builder
+ * @param lineConsumer The {@linkplain Consumer} the lines will be forwarded to
+ * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
+ * Used to determine the moment the target app is
+ * properly warmed-up.
+ * It can be null - in that case the warmup is skipped.
+ * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever
+ * @param unit The timeout {@linkplain TimeUnit}
+ * @return Returns the initialized {@linkplain Process}
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws TimeoutException
+ */
+ public static Process startProcess(String name,
+ ProcessBuilder processBuilder,
+ final Consumer lineConsumer,
+ final Predicate linePredicate,
+ long timeout,
+ TimeUnit unit)
+ throws IOException, InterruptedException, TimeoutException {
+ System.out.println("["+name+"]:" + processBuilder.command().stream().collect(Collectors.joining(" ")));
+ Process p = processBuilder.start();
+ StreamPumper stdout = new StreamPumper(p.getInputStream());
+ StreamPumper stderr = new StreamPumper(p.getErrorStream());
+
+ stdout.addPump(new LineForwarder(name, System.out));
+ stderr.addPump(new LineForwarder(name, System.err));
+ if (lineConsumer != null) {
+ StreamPumper.LinePump pump = new StreamPumper.LinePump() {
+ @Override
+ protected void processLine(String line) {
+ lineConsumer.accept(line);
+ }
+ };
+ stdout.addPump(pump);
+ stderr.addPump(pump);
+ }
+
+
+ CountDownLatch latch = new CountDownLatch(1);
+ if (linePredicate != null) {
+ StreamPumper.LinePump pump = new StreamPumper.LinePump() {
+ @Override
+ protected void processLine(String line) {
+ if (latch.getCount() > 0 && linePredicate.test(line)) {
+ latch.countDown();
+ }
+ }
+ };
+ stdout.addPump(pump);
+ stderr.addPump(pump);
+ } else {
+ latch.countDown();
+ }
+ final Future stdoutTask = stdout.process();
+ final Future stderrTask = stderr.process();
+
+ try {
+ if (timeout > -1) {
+ if (timeout == 0) {
+ latch.await();
+ } else {
+ if (!latch.await(Utils.adjustTimeout(timeout), unit)) {
+ throw new TimeoutException();
+ }
+ }
+ }
+ } catch (TimeoutException | InterruptedException e) {
+ System.err.println("Failed to start a process (thread dump follows)");
+ for(Map.Entry s : Thread.getAllStackTraces().entrySet()) {
+ printStack(s.getKey(), s.getValue());
+ }
+
+ if (p.isAlive()) {
+ p.destroyForcibly();
+ }
+
+ stdoutTask.cancel(true);
+ stderrTask.cancel(true);
+ throw e;
+ }
+
+ return new ProcessImpl(p, stdoutTask, stderrTask);
+ }
+
+ /**
+ * Starts a process from its builder.
+ * The default redirects of STDOUT and STDERR are started
+ *
+ * It is possible to wait for the process to get to a warmed-up state
+ * via {@linkplain Predicate} condition on the STDOUT. The warm-up will
+ * wait indefinitely.
+ *
+ * @param name The process name
+ * @param processBuilder The process builder
+ * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
+ * Used to determine the moment the target app is
+ * properly warmed-up.
+ * It can be null - in that case the warmup is skipped.
+ * @return Returns the initialized {@linkplain Process}
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws TimeoutException
+ */
+ @SuppressWarnings("overloads")
+ public static Process startProcess(String name,
+ ProcessBuilder processBuilder,
+ final Predicate linePredicate)
+ throws IOException, InterruptedException, TimeoutException {
+ return startProcess(name, processBuilder, linePredicate, 0, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Get the process id of the current running Java process
+ *
+ * @return Process id
+ */
+ public static long getProcessId() throws Exception {
+ return ProcessHandle.current().getPid();
+ }
+
+ /**
+ * Get platform specific VM arguments (e.g. -d64 on 64bit Solaris)
+ *
+ * @return String[] with platform specific arguments, empty if there are
+ * none
+ */
+ public static String[] getPlatformSpecificVMArgs() {
+
+ if (Platform.is64bit() && Platform.isSolaris()) {
+ return new String[] { "-d64" };
+ }
+
+ return new String[] {};
+ }
+
+
+ /**
+ * Create ProcessBuilder using the java launcher from the jdk to be tested and
+ * with any platform specific arguments prepended
+ */
+ public static ProcessBuilder createJavaProcessBuilder(String... command) throws Exception {
+ return createJavaProcessBuilder(false, command);
+ }
+
+ /**
+ * Create ProcessBuilder using the java launcher from the jdk to be tested,
+ * and with any platform specific arguments prepended.
+ *
+ * @param addTestVmAndJavaOptions If true, adds test.vm.opts and test.java.opts
+ * to the java arguments.
+ * @param command Arguments to pass to the java command.
+ * @return The ProcessBuilder instance representing the java command.
+ */
+ public static ProcessBuilder createJavaProcessBuilder(boolean addTestVmAndJavaOptions, String... command) throws Exception {
+ String javapath = JDKToolFinder.getJDKTool("java");
+
+ ArrayList args = new ArrayList<>();
+ args.add(javapath);
+ Collections.addAll(args, getPlatformSpecificVMArgs());
+
+ if (addTestVmAndJavaOptions) {
+ // -cp is needed to make sure the same classpath is used whether the test is
+ // run in AgentVM mode or OtherVM mode. It was added to the hotspot version
+ // of this API as part of 8077608. However, for the jdk version it is only
+ // added when addTestVmAndJavaOptions is true in order to minimize
+ // disruption to existing JDK tests, which have yet to be tested with -cp
+ // being added. At some point -cp should always be added to be consistent
+ // with what the hotspot version does.
+ args.add("-cp");
+ args.add(System.getProperty("java.class.path"));
+ Collections.addAll(args, Utils.getTestJavaOpts());
+ }
+
+ Collections.addAll(args, command);
+
+ // Reporting
+ StringBuilder cmdLine = new StringBuilder();
+ for (String cmd : args)
+ cmdLine.append(cmd).append(' ');
+ System.out.println("Command line: [" + cmdLine.toString() + "]");
+
+ return new ProcessBuilder(args.toArray(new String[args.size()]));
+ }
+
+ private static void printStack(Thread t, StackTraceElement[] stack) {
+ System.out.println("\t" + t +
+ " stack: (length = " + stack.length + ")");
+ if (t != null) {
+ for (StackTraceElement stack1 : stack) {
+ System.out.println("\t" + stack1);
+ }
+ System.out.println();
+ }
+ }
+
+ /**
+ * Executes a test jvm process, waits for it to finish and returns the process output.
+ * The default jvm options from jtreg, test.vm.opts and test.java.opts, are added.
+ * The java from the test.jdk is used to execute the command.
+ *
+ * The command line will be like:
+ * {test.jdk}/bin/java {test.vm.opts} {test.java.opts} cmds
+ *
+ * The jvm process will have exited before this method returns.
+ *
+ * @param cmds User specifed arguments.
+ * @return The output from the process.
+ */
+ public static OutputAnalyzer executeTestJvm(String... cmds) throws Exception {
+ ProcessBuilder pb = createJavaProcessBuilder(Utils.addTestJavaOpts(cmds));
+ return executeProcess(pb);
+ }
+
+ /**
+ * Executes a process, waits for it to finish and returns the process output.
+ * The process will have exited before this method returns.
+ * @param pb The ProcessBuilder to execute.
+ * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
+ */
+ public static OutputAnalyzer executeProcess(ProcessBuilder pb) throws Exception {
+ OutputAnalyzer output = null;
+ Process p = null;
+ boolean failed = false;
+ try {
+ p = pb.start();
+ output = new OutputAnalyzer(p);
+ p.waitFor();
+
+ return output;
+ } catch (Throwable t) {
+ if (p != null) {
+ p.destroyForcibly().waitFor();
+ }
+
+ failed = true;
+ System.out.println("executeProcess() failed: " + t);
+ throw t;
+ } finally {
+ if (failed) {
+ System.err.println(getProcessLog(pb, output));
+ }
+ }
+ }
+
+ /**
+ * Executes a process, waits for it to finish and returns the process output.
+ *
+ * The process will have exited before this method returns.
+ *
+ * @param cmds The command line to execute.
+ * @return The output from the process.
+ */
+ public static OutputAnalyzer executeProcess(String... cmds) throws Throwable {
+ return executeProcess(new ProcessBuilder(cmds));
+ }
+
+ /**
+ * Used to log command line, stdout, stderr and exit code from an executed process.
+ * @param pb The executed process.
+ * @param output The output from the process.
+ */
+ public static String getProcessLog(ProcessBuilder pb, OutputAnalyzer output) {
+ String stderr = output == null ? "null" : output.getStderr();
+ String stdout = output == null ? "null" : output.getStdout();
+ String exitValue = output == null ? "null": Integer.toString(output.getExitValue());
+ StringBuilder logMsg = new StringBuilder();
+ final String nl = System.getProperty("line.separator");
+ logMsg.append("--- ProcessLog ---" + nl);
+ logMsg.append("cmd: " + getCommandLine(pb) + nl);
+ logMsg.append("exitvalue: " + exitValue + nl);
+ logMsg.append("stderr: " + stderr + nl);
+ logMsg.append("stdout: " + stdout + nl);
+
+ return logMsg.toString();
+ }
+
+ /**
+ * @return The full command line for the ProcessBuilder.
+ */
+ public static String getCommandLine(ProcessBuilder pb) {
+ if (pb == null) {
+ return "null";
+ }
+ StringBuilder cmd = new StringBuilder();
+ for (String s : pb.command()) {
+ cmd.append(s).append(" ");
+ }
+ return cmd.toString().trim();
+ }
+
+ /**
+ * Executes a process, waits for it to finish, prints the process output
+ * to stdout, and returns the process output.
+ *
+ * The process will have exited before this method returns.
+ *
+ * @param cmds The command line to execute.
+ * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
+ */
+ public static OutputAnalyzer executeCommand(String... cmds)
+ throws Throwable {
+ String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" "));
+ System.out.println("Command line: [" + cmdLine + "]");
+ OutputAnalyzer analyzer = ProcessTools.executeProcess(cmds);
+ System.out.println(analyzer.getOutput());
+ return analyzer;
+ }
+
+ /**
+ * Executes a process, waits for it to finish, prints the process output
+ * to stdout and returns the process output.
+ *
+ * The process will have exited before this method returns.
+ *
+ * @param pb The ProcessBuilder to execute.
+ * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
+ */
+ public static OutputAnalyzer executeCommand(ProcessBuilder pb)
+ throws Throwable {
+ String cmdLine = pb.command().stream().collect(Collectors.joining(" "));
+ System.out.println("Command line: [" + cmdLine + "]");
+ OutputAnalyzer analyzer = ProcessTools.executeProcess(pb);
+ System.out.println(analyzer.getOutput());
+ return analyzer;
+ }
+
+ private static class ProcessImpl extends Process {
+
+ private final Process p;
+ private final Future stdoutTask;
+ private final Future stderrTask;
+
+ public ProcessImpl(Process p, Future stdoutTask, Future stderrTask) {
+ this.p = p;
+ this.stdoutTask = stdoutTask;
+ this.stderrTask = stderrTask;
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return p.getOutputStream();
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return p.getInputStream();
+ }
+
+ @Override
+ public InputStream getErrorStream() {
+ return p.getErrorStream();
+ }
+
+ @Override
+ public int waitFor() throws InterruptedException {
+ int rslt = p.waitFor();
+ waitForStreams();
+ return rslt;
+ }
+
+ @Override
+ public int exitValue() {
+ return p.exitValue();
+ }
+
+ @Override
+ public void destroy() {
+ p.destroy();
+ }
+
+ @Override
+ public long getPid() {
+ return p.getPid();
+ }
+
+ @Override
+ public boolean isAlive() {
+ return p.isAlive();
+ }
+
+ @Override
+ public Process destroyForcibly() {
+ return p.destroyForcibly();
+ }
+
+ @Override
+ public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException {
+ boolean rslt = p.waitFor(timeout, unit);
+ if (rslt) {
+ waitForStreams();
+ }
+ return rslt;
+ }
+
+ private void waitForStreams() throws InterruptedException {
+ try {
+ stdoutTask.get();
+ } catch (ExecutionException e) {
+ }
+ try {
+ stderrTask.get();
+ } catch (ExecutionException e) {
+ }
+ }
+ }
+}
diff -r c742968c922b -r 24c22a1b14ff test/lib/share/classes/jdk/test/lib/process/StreamPumper.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/lib/share/classes/jdk/test/lib/process/StreamPumper.java Mon Dec 07 17:04:33 2015 +0000
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.test.lib.process;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public final class StreamPumper implements Runnable {
+
+ private static final int BUF_SIZE = 256;
+
+ /**
+ * Pump will be called by the StreamPumper to process the incoming data
+ */
+ abstract public static class Pump {
+ abstract void register(StreamPumper d);
+ }
+
+ /**
+ * OutputStream -> Pump adapter
+ */
+ final public static class StreamPump extends Pump {
+ private final OutputStream out;
+ public StreamPump(OutputStream out) {
+ this.out = out;
+ }
+
+ @Override
+ void register(StreamPumper sp) {
+ sp.addOutputStream(out);
+ }
+ }
+
+ /**
+ * Used to process the incoming data line-by-line
+ */
+ abstract public static class LinePump extends Pump {
+ @Override
+ final void register(StreamPumper sp) {
+ sp.addLineProcessor(this);
+ }
+
+ abstract protected void processLine(String line);
+ }
+
+ private final InputStream in;
+ private final Set outStreams = new HashSet<>();
+ private final Set linePumps = new HashSet<>();
+
+ private final AtomicBoolean processing = new AtomicBoolean(false);
+ private final FutureTask processingTask = new FutureTask<>(this, null);
+
+ public StreamPumper(InputStream in) {
+ this.in = in;
+ }
+
+ /**
+ * Create a StreamPumper that reads from in and writes to out.
+ *
+ * @param in The stream to read from.
+ * @param out The stream to write to.
+ */
+ public StreamPumper(InputStream in, OutputStream out) {
+ this(in);
+ this.addOutputStream(out);
+ }
+
+ /**
+ * Implements Thread.run(). Continuously read from {@code in} and write to
+ * {@code out} until {@code in} has reached end of stream. Abort on
+ * interruption. Abort on IOExceptions.
+ */
+ @Override
+ public void run() {
+ try (BufferedInputStream is = new BufferedInputStream(in)) {
+ ByteArrayOutputStream lineBos = new ByteArrayOutputStream();
+ byte[] buf = new byte[BUF_SIZE];
+ int len = 0;
+ int linelen = 0;
+
+ while ((len = is.read(buf)) > 0 && !Thread.interrupted()) {
+ for(OutputStream out : outStreams) {
+ out.write(buf, 0, len);
+ }
+ if (!linePumps.isEmpty()) {
+ int i = 0;
+ int lastcrlf = -1;
+ while (i < len) {
+ if (buf[i] == '\n' || buf[i] == '\r') {
+ int bufLinelen = i - lastcrlf - 1;
+ if (bufLinelen > 0) {
+ lineBos.write(buf, lastcrlf + 1, bufLinelen);
+ }
+ linelen += bufLinelen;
+
+ if (linelen > 0) {
+ lineBos.flush();
+ final String line = lineBos.toString();
+ linePumps.stream().forEach((lp) -> {
+ lp.processLine(line);
+ });
+ lineBos.reset();
+ linelen = 0;
+ }
+ lastcrlf = i;
+ }
+
+ i++;
+ }
+ if (lastcrlf == -1) {
+ lineBos.write(buf, 0, len);
+ linelen += len;
+ } else if (lastcrlf < len - 1) {
+ lineBos.write(buf, lastcrlf + 1, len - lastcrlf - 1);
+ linelen += len - lastcrlf - 1;
+ }
+ }
+ }
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ for(OutputStream out : outStreams) {
+ try {
+ out.flush();
+ } catch (IOException e) {}
+ }
+ try {
+ in.close();
+ } catch (IOException e) {}
+ }
+ }
+
+ final void addOutputStream(OutputStream out) {
+ outStreams.add(out);
+ }
+
+ final void addLineProcessor(LinePump lp) {
+ linePumps.add(lp);
+ }
+
+ final public StreamPumper addPump(Pump ... pump) {
+ if (processing.get()) {
+ throw new IllegalStateException("Can not modify pumper while " +
+ "processing is in progress");
+ }
+ for(Pump p : pump) {
+ p.register(this);
+ }
+ return this;
+ }
+
+ final public Future process() {
+ if (!processing.compareAndSet(false, true)) {
+ throw new IllegalStateException("Can not re-run the processing");
+ }
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ processingTask.run();
+ }
+ });
+ t.setDaemon(true);
+ t.start();
+
+ return processingTask;
+ }
+}