You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ti...@apache.org on 2019/12/21 23:53:42 UTC
[maven-surefire] 02/06: CommandlineExecutor with
java.nio.channels.Channel used in Pipes and TCP ServerSocketChannel
(instead of maven-shared-utils)
This is an automated email from the ASF dual-hosted git repository.
tibordigana pushed a commit to branch cli
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git
commit 26caa997fe2450fb5ac3a59ea0e43a21444840fd
Author: tibordigana <ti...@apache.org>
AuthorDate: Tue Nov 26 01:09:31 2019 +0100
CommandlineExecutor with java.nio.channels.Channel used in Pipes and TCP ServerSocketChannel (instead of maven-shared-utils)
---
.../extensions/util/CommandlineExecutor.java | 122 +++++++++++++++++++++
.../extensions/util/CommandlineStreams.java | 81 ++++++++++++++
.../util/FlushableWritableByteChannel.java | 67 +++++++++++
.../extensions/util/LineConsumerThread.java | 94 ++++++++++++++++
4 files changed, 364 insertions(+)
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineExecutor.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineExecutor.java
new file mode 100644
index 0000000..9415bfe
--- /dev/null
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineExecutor.java
@@ -0,0 +1,122 @@
+package org.apache.maven.surefire.extensions.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.shared.utils.cli.CommandLineException;
+import org.apache.maven.surefire.shared.utils.cli.Commandline;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+
+import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
+import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.removeShutdownHook;
+
+/**
+ * Programming model with this class:
+ * <pre> {@code
+ * try ( CommandlineExecutor exec = new CommandlineExecutor( cli, runAfterProcessTermination, endOfStreamsCountdown );
+ * CommandlineStreams streams = exec.execute() )
+ * {
+ * // register exec in the shutdown hook to destroy pending process
+ *
+ * // register streams in the shutdown hook to close all three streams
+ *
+ * ReadableByteChannel stdOut = streams.getStdOutChannel();
+ * ReadableByteChannel stdErr = streams.getStdErrChannel();
+ * WritableByteChannel stdIn = streams.getStdInChannel();
+ * // lineConsumerThread = new LineConsumerThread( ..., stdErr, ..., endOfStreamsCountdown );
+ * // lineConsumerThread.start();
+ *
+ * // stdIn.write( ... );
+ *
+ * int exitCode = exec.awaitExit();
+ * // process exitCode
+ * }
+ * catch ( InterruptedException | IOException | CommandLineException e )
+ * {
+ * // lineConsumerThread.disable();
+ * // handle the exceptions
+ * }
+ * } </pre>
+ */
+public class CommandlineExecutor implements Closeable
+{
+ private final Commandline cli;
+ private final CountDownLatch endOfStreamsCountdown;
+ private final Closeable closeAfterProcessTermination;
+ private Process process;
+ private volatile Thread shutdownHook;
+
+ public CommandlineExecutor( Commandline cli,
+ Closeable closeAfterProcessTermination, CountDownLatch endOfStreamsCountdown )
+ {
+ this.cli = cli;
+ this.closeAfterProcessTermination = closeAfterProcessTermination;
+ this.endOfStreamsCountdown = endOfStreamsCountdown;
+ }
+
+ public CommandlineStreams execute() throws CommandLineException
+ {
+ process = cli.execute();
+ shutdownHook = new ProcessHook( process );
+ addShutDownHook( shutdownHook );
+ return new CommandlineStreams( process );
+ }
+
+ public int awaitExit() throws InterruptedException, IOException
+ {
+ int exitCode = process.waitFor();
+ closeAfterProcessTermination.close();
+ endOfStreamsCountdown.await();
+ return exitCode;
+ }
+
+ @Override
+ public void close()
+ {
+ if ( shutdownHook != null )
+ {
+ shutdownHook.run();
+ removeShutdownHook( shutdownHook );
+ shutdownHook = null;
+ }
+ }
+
+ private static class ProcessHook extends Thread
+ {
+ private final Process process;
+
+ private ProcessHook( Process process )
+ {
+ super( "cli-shutdown-hook" );
+ this.process = process;
+ setContextClassLoader( null );
+ setDaemon( true );
+ }
+
+ /** {@inheritDoc} */
+ public void run()
+ {
+ process.destroy();
+ }
+ }
+
+}
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
new file mode 100644
index 0000000..e60b2c6
--- /dev/null
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
@@ -0,0 +1,81 @@
+package org.apache.maven.surefire.extensions.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.Channel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+
+import static java.nio.channels.Channels.newChannel;
+import static org.apache.maven.surefire.extensions.util.FlushableWritableByteChannel.newFlushableChannel;
+
+/**
+ *
+ */
+public final class CommandlineStreams implements Closeable
+{
+ private final ReadableByteChannel stdOutChannel;
+ private final ReadableByteChannel stdErrChannel;
+ private final WritableByteChannel stdInChannel;
+ private volatile boolean closed;
+
+ public CommandlineStreams( Process process )
+ {
+ InputStream stdOutStream = process.getInputStream();
+ stdOutChannel = newChannel( stdOutStream );
+ InputStream stdErrStream = process.getErrorStream();
+ stdErrChannel = newChannel( stdErrStream );
+ stdInChannel = newFlushableChannel( process.getOutputStream() );
+ }
+
+ public ReadableByteChannel getStdOutChannel()
+ {
+ return stdOutChannel;
+ }
+
+ public ReadableByteChannel getStdErrChannel()
+ {
+ return stdErrChannel;
+ }
+
+ public WritableByteChannel getStdInChannel()
+ {
+ return stdInChannel;
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ if ( closed )
+ {
+ return;
+ }
+
+ try ( Channel c1 = stdOutChannel;
+ Channel c2 = stdErrChannel;
+ Channel c3 = stdInChannel )
+ {
+ closed = true;
+ }
+ }
+}
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/FlushableWritableByteChannel.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/FlushableWritableByteChannel.java
new file mode 100644
index 0000000..b769407
--- /dev/null
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/FlushableWritableByteChannel.java
@@ -0,0 +1,67 @@
+package org.apache.maven.surefire.extensions.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
+
+import static java.nio.channels.Channels.newChannel;
+
+/**
+ *
+ */
+final class FlushableWritableByteChannel implements WritableByteChannel
+{
+ private final OutputStream os;
+ private final WritableByteChannel channel;
+
+ private FlushableWritableByteChannel( OutputStream os )
+ {
+ this.os = os;
+ this.channel = newChannel( os );
+ }
+
+ static synchronized WritableByteChannel newFlushableChannel( OutputStream os )
+ {
+ return new FlushableWritableByteChannel( os );
+ }
+
+ @Override
+ public int write( ByteBuffer src ) throws IOException
+ {
+ int countWrittenBytes = channel.write( src );
+ os.flush();
+ return countWrittenBytes;
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return channel.isOpen();
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ channel.close();
+ }
+}
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/LineConsumerThread.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/LineConsumerThread.java
new file mode 100644
index 0000000..771cdc5
--- /dev/null
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/LineConsumerThread.java
@@ -0,0 +1,94 @@
+package org.apache.maven.surefire.extensions.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
+
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.Charset;
+import java.util.Scanner;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ *
+ */
+public final class LineConsumerThread extends Thread
+{
+ private final Scanner scanner;
+ private final StreamConsumer consumer;
+ private final CountDownLatch endOfStreamsCountdown;
+ private volatile boolean disabled;
+
+ public LineConsumerThread( String threadName,
+ ReadableByteChannel channel, StreamConsumer consumer,
+ CountDownLatch endOfStreamsCountdown )
+ {
+ this( threadName, channel, consumer, Charset.defaultCharset(), endOfStreamsCountdown );
+ }
+
+ public LineConsumerThread( String threadName,
+ ReadableByteChannel channel, StreamConsumer consumer, Charset encoding,
+ CountDownLatch endOfStreamsCountdown )
+ {
+ setName( threadName );
+ setDaemon( true );
+ scanner = new Scanner( channel, encoding.name() );
+ this.consumer = consumer;
+ this.endOfStreamsCountdown = endOfStreamsCountdown;
+ }
+
+ @Override
+ public void run()
+ {
+ try ( Scanner stream = scanner )
+ {
+ boolean isError = false;
+ while ( stream.hasNextLine() )
+ {
+ try
+ {
+ String line = stream.nextLine();
+ isError |= stream.ioException() != null;
+ if ( !isError && !disabled )
+ {
+ consumer.consumeLine( line );
+ }
+ }
+ catch ( IllegalStateException e )
+ {
+ isError = true;
+ }
+ }
+ }
+ catch ( IllegalStateException e )
+ {
+ // not needed
+ }
+ finally
+ {
+ endOfStreamsCountdown.countDown();
+ }
+ }
+
+ public void disable()
+ {
+ disabled = true;
+ }
+}