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;
+    }
+}