This is an automated email from the ASF dual-hosted git repository. tibordigana pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven-surefire.git
commit dcccd2330e5365a575a2b782854d58b5ed2f98b0 Author: Tibor Digana <[email protected]> AuthorDate: Mon Apr 5 13:53:44 2021 +0200 [SUREFIRE-1881] Adds additional debug log and fork connection timeout --- .../plugin/surefire/booterclient/ForkStarter.java | 34 +-- .../extensions/InvalidSessionIdException.java | 3 +- .../surefire/extensions/LegacyForkChannel.java | 40 +++- .../surefire/extensions/SurefireForkChannel.java | 215 ++++++++++++++----- .../maven/plugin/surefire/extensions/E2ETest.java | 237 +++++++++++++++------ .../maven/surefire/extensions/ForkChannelTest.java | 6 +- .../AbstractNoninterruptibleReadableChannel.java | 10 +- .../surefire/extensions/CloseableDaemonThread.java | 5 +- ...CloseableDaemonThread.java => Completable.java} | 23 +- .../maven/surefire/extensions/ForkChannel.java | 36 ++-- .../{CloseableDaemonThread.java => Stoppable.java} | 13 +- .../extensions/util/CountDownLauncher.java | 33 ++- .../maven/surefire/its/jiras/Surefire1881.java | 22 +- .../src/test/resources/surefire-1881/pom.xml | 106 +++++++++ .../src/main/java/de/scrum_master/dummy/Agent.java | 49 +++++ .../test/java/de/scrum_master/dummy/AgentIT.java | 23 ++ 16 files changed, 642 insertions(+), 213 deletions(-) diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java index 200d2a8..a999069 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java @@ -41,11 +41,11 @@ import org.apache.maven.surefire.api.booter.Shutdown; import org.apache.maven.surefire.booter.StartupConfiguration; import org.apache.maven.surefire.booter.SurefireBooterForkException; import org.apache.maven.surefire.booter.SurefireExecutionException; -import org.apache.maven.surefire.extensions.CloseableDaemonThread; import org.apache.maven.surefire.extensions.EventHandler; import org.apache.maven.surefire.extensions.ForkChannel; import org.apache.maven.surefire.extensions.ForkNodeFactory; import org.apache.maven.surefire.api.fork.ForkNodeArguments; +import org.apache.maven.surefire.extensions.Stoppable; import org.apache.maven.surefire.extensions.util.CommandlineExecutor; import org.apache.maven.surefire.extensions.util.CommandlineStreams; import org.apache.maven.surefire.extensions.util.CountdownCloseable; @@ -628,31 +628,24 @@ public class ForkStarter Integer result = null; RunResult runResult = null; SurefireBooterForkException booterForkException = null; - CloseableDaemonThread in = null; - CloseableDaemonThread out = null; - CloseableDaemonThread err = null; + Stoppable err = null; DefaultReporterFactory reporter = forkClient.getDefaultReporterFactory(); currentForkClients.add( forkClient ); CountdownCloseable countdownCloseable = new CountdownCloseable( eventConsumer, forkChannel.getCountdownCloseablePermits() ); try ( CommandlineExecutor exec = new CommandlineExecutor( cli, countdownCloseable ) ) { + forkChannel.tryConnectToClient(); CommandlineStreams streams = exec.execute(); closer.addCloseable( streams ); - forkChannel.connectToClient(); - log.debug( "Fork Channel [" + forkNumber + "] connected to the client." ); + err = bindErrorStream( forkNumber, countdownCloseable, streams ); - in = forkChannel.bindCommandReader( commandReader, streams.getStdInChannel() ); - in.start(); + forkChannel.bindCommandReader( commandReader, streams.getStdInChannel() ); - out = forkChannel.bindEventHandler( eventConsumer, countdownCloseable, streams.getStdOutChannel() ); - out.start(); + forkChannel.bindEventHandler( eventConsumer, countdownCloseable, streams.getStdOutChannel() ); - EventHandler<String> errConsumer = new NativeStdErrStreamConsumer( log ); - err = new LineConsumerThread( "fork-" + forkNumber + "-err-thread", streams.getStdErrChannel(), - errConsumer, countdownCloseable ); - err.start(); + log.debug( "Fork Channel [" + forkNumber + "] connected to the client." ); result = exec.awaitExit(); @@ -670,8 +663,7 @@ public class ForkStarter { log.error( "Closing the streams after (InterruptedException) '" + e.getLocalizedMessage() + "'" ); // maybe implement it in the Future.cancel() of the extension or similar - in.disable(); - out.disable(); + forkChannel.disable(); err.disable(); } catch ( Exception e ) @@ -761,6 +753,16 @@ public class ForkStarter return runResult; } + private Stoppable bindErrorStream( int forkNumber, CountdownCloseable countdownCloseable, + CommandlineStreams streams ) + { + EventHandler<String> errConsumer = new NativeStdErrStreamConsumer( log ); + LineConsumerThread stdErr = new LineConsumerThread( "fork-" + forkNumber + "-err-thread", + streams.getStdErrChannel(), errConsumer, countdownCloseable ); + stdErr.start(); + return stdErr; + } + private Iterable<Class<?>> getSuitesIterator() throws SurefireBooterForkException { diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/InvalidSessionIdException.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/InvalidSessionIdException.java index 2ceaf12..30664e2 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/InvalidSessionIdException.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/InvalidSessionIdException.java @@ -25,7 +25,8 @@ import org.apache.maven.surefire.extensions.util.CommandlineExecutor; import java.io.IOException; /** - * After the authentication has failed, {@link ForkChannel#connectToClient()} throws {@link InvalidSessionIdException} + * After the authentication has failed, {@link ForkChannel#tryConnectToClient()} + * throws {@link InvalidSessionIdException} * and {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter} should close {@link CommandlineExecutor}. * * @since 3.0.0-M5 diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java index e18f77d..b754535 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/LegacyForkChannel.java @@ -41,13 +41,16 @@ import java.nio.channels.WritableByteChannel; */ final class LegacyForkChannel extends ForkChannel { + private CloseableDaemonThread commandReaderBindings; + private CloseableDaemonThread eventHandlerBindings; + LegacyForkChannel( @Nonnull ForkNodeArguments arguments ) { super( arguments ); } @Override - public void connectToClient() + public void tryConnectToClient() { } @@ -64,20 +67,37 @@ final class LegacyForkChannel extends ForkChannel } @Override - public CloseableDaemonThread bindCommandReader( @Nonnull CommandReader commands, - WritableByteChannel stdIn ) + public void bindCommandReader( @Nonnull CommandReader commands, WritableByteChannel stdIn ) + { + ForkNodeArguments args = getArguments(); + String threadName = "commands-fork-" + args.getForkChannelId(); + commandReaderBindings = new StreamFeeder( threadName, stdIn, commands, args.getConsoleLogger() ); + commandReaderBindings.start(); + } + + @Override + public void bindEventHandler( @Nonnull EventHandler<Event> eventHandler, + @Nonnull CountdownCloseable countdownCloseable, + ReadableByteChannel stdOut ) { - return new StreamFeeder( "std-in-fork-" + getArguments().getForkChannelId(), stdIn, commands, - getArguments().getConsoleLogger() ); + ForkNodeArguments args = getArguments(); + String threadName = "fork-" + args.getForkChannelId() + "-event-thread"; + eventHandlerBindings = new EventConsumerThread( threadName, stdOut, eventHandler, countdownCloseable, args ); + eventHandlerBindings.start(); } @Override - public CloseableDaemonThread bindEventHandler( @Nonnull EventHandler<Event> eventHandler, - @Nonnull CountdownCloseable countdownCloseable, - ReadableByteChannel stdOut ) + public void disable() { - return new EventConsumerThread( "fork-" + getArguments().getForkChannelId() + "-event-thread", stdOut, - eventHandler, countdownCloseable, getArguments() ); + if ( eventHandlerBindings != null ) + { + eventHandlerBindings.disable(); + } + + if ( commandReaderBindings != null ) + { + commandReaderBindings.disable(); + } } @Override diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java index 640d562..e070679 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java @@ -21,11 +21,12 @@ package org.apache.maven.plugin.surefire.extensions; import org.apache.maven.plugin.surefire.booterclient.output.NativeStdOutStreamConsumer; import org.apache.maven.surefire.api.event.Event; +import org.apache.maven.surefire.api.fork.ForkNodeArguments; import org.apache.maven.surefire.extensions.CloseableDaemonThread; import org.apache.maven.surefire.extensions.CommandReader; import org.apache.maven.surefire.extensions.EventHandler; import org.apache.maven.surefire.extensions.ForkChannel; -import org.apache.maven.surefire.api.fork.ForkNodeArguments; +import org.apache.maven.surefire.extensions.util.CountDownLauncher; import org.apache.maven.surefire.extensions.util.CountdownCloseable; import org.apache.maven.surefire.extensions.util.LineConsumerThread; @@ -44,6 +45,7 @@ import java.nio.channels.WritableByteChannel; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import static java.net.StandardSocketOptions.SO_KEEPALIVE; import static java.net.StandardSocketOptions.SO_REUSEADDR; @@ -56,11 +58,13 @@ import static org.apache.maven.surefire.api.util.internal.Channels.newChannel; import static org.apache.maven.surefire.api.util.internal.Channels.newInputStream; import static org.apache.maven.surefire.api.util.internal.Channels.newOutputStream; import static org.apache.maven.surefire.api.util.internal.DaemonThreadFactory.newDaemonThreadFactory; +import static org.apache.maven.surefire.shared.lang3.StringUtils.isBlank; +import static org.apache.maven.surefire.shared.lang3.StringUtils.isNotBlank; /** * The TCP/IP server accepting only one client connection. The forked JVM connects to the server using the * {@link #getForkNodeConnectionString() connection string}. - * The main purpose of this class is to {@link #connectToClient() conect with tthe client}, bind the + * The main purpose of this class is to {@link #tryConnectToClient() conect with tthe client}, bind the * {@link #bindCommandReader(CommandReader, WritableByteChannel) command reader} to the internal socket's * {@link java.io.InputStream}, and bind the * {@link #bindEventHandler(EventHandler, CountdownCloseable, ReadableByteChannel) event handler} writing the event @@ -79,8 +83,13 @@ final class SurefireForkChannel extends ForkChannel private final String localHost; private final int localPort; private final String sessionId; - private volatile AsynchronousSocketChannel worker; + private final Bindings bindings = new Bindings( 2 ); + private volatile Future<AsynchronousSocketChannel> session; private volatile LineConsumerThread out; + private volatile CloseableDaemonThread commandReaderBindings; + private volatile CloseableDaemonThread eventHandlerBindings; + private volatile EventBindings eventBindings; + private volatile CommandBindings commandBindings; SurefireForkChannel( @Nonnull ForkNodeArguments arguments ) throws IOException { @@ -96,45 +105,108 @@ final class SurefireForkChannel extends ForkChannel } @Override - public void connectToClient() throws IOException + public void tryConnectToClient() { - if ( worker != null ) + if ( session != null ) { throw new IllegalStateException( "already accepted TCP client connection" ); } + session = server.accept(); + } - try + @Override + public String getForkNodeConnectionString() + { + return "tcp://" + localHost + ":" + localPort + ( isBlank( sessionId ) ? "" : "?sessionId=" + sessionId ); + } + + @Override + public int getCountdownCloseablePermits() + { + return 3; + } + + @Override + public void bindCommandReader( @Nonnull CommandReader commands, WritableByteChannel stdIn ) + throws IOException, InterruptedException + { + commandBindings = new CommandBindings( commands ); + + bindings.countDown(); + } + + @Override + public void bindEventHandler( @Nonnull EventHandler<Event> eventHandler, + @Nonnull CountdownCloseable countdown, + ReadableByteChannel stdOut ) + throws IOException, InterruptedException + { + ForkNodeArguments args = getArguments(); + out = new LineConsumerThread( "fork-" + args.getForkChannelId() + "-out-thread", stdOut, + new NativeStdOutStreamConsumer( args.getConsoleLogger() ), countdown ); + out.start(); + + eventBindings = new EventBindings( eventHandler, countdown ); + + bindings.countDown(); + } + + @Override + public void disable() + { + if ( eventHandlerBindings != null ) { - worker = server.accept().get(); - verifySessionId(); + eventHandlerBindings.disable(); } - catch ( InterruptedException e ) + + if ( commandReaderBindings != null ) { - throw new IOException( e.getLocalizedMessage(), e ); + commandReaderBindings.disable(); } - catch ( ExecutionException e ) + } + + @Override + public void close() throws IOException + { + //noinspection unused,EmptyTryBlock,EmptyTryBlock + try ( Closeable c1 = getChannel(); Closeable c2 = server; Closeable c3 = out ) { - throw new IOException( e.getLocalizedMessage(), e.getCause() ); + // only close all channels + } + catch ( InterruptedException e ) + { + Throwable cause = e.getCause(); + throw cause instanceof IOException ? (IOException) cause : new IOException( cause ); } } - private void verifySessionId() throws InterruptedException, ExecutionException, IOException + private void verifySessionId() throws InterruptedException, IOException { - ByteBuffer buffer = ByteBuffer.allocate( sessionId.length() ); - int read; - do - { - read = worker.read( buffer ).get(); - } while ( read != -1 && buffer.hasRemaining() ); - if ( read == -1 ) + try { - throw new IOException( "Channel closed while verifying the client." ); + ByteBuffer buffer = ByteBuffer.allocate( sessionId.length() ); + int read; + do + { + read = getChannel().read( buffer ).get(); + } while ( read != -1 && buffer.hasRemaining() ); + + if ( read == -1 ) + { + throw new IOException( "Channel closed while verifying the client." ); + } + + ( (Buffer) buffer ).flip(); + String clientSessionId = new String( buffer.array(), US_ASCII ); + if ( !clientSessionId.equals( sessionId ) ) + { + throw new InvalidSessionIdException( clientSessionId, sessionId ); + } } - ( (Buffer) buffer ).flip(); - String clientSessionId = new String( buffer.array(), US_ASCII ); - if ( !clientSessionId.equals( sessionId ) ) + catch ( ExecutionException e ) { - throw new InvalidSessionIdException( clientSessionId, sessionId ); + Throwable cause = e.getCause(); + throw cause instanceof IOException ? (IOException) cause : new IOException( cause ); } } @@ -151,51 +223,80 @@ final class SurefireForkChannel extends ForkChannel } } - @Override - public String getForkNodeConnectionString() + private class EventBindings { - return "tcp://" + localHost + ":" + localPort + "?sessionId=" + sessionId; - } + private final EventHandler<Event> eventHandler; + private final CountdownCloseable countdown; - @Override - public int getCountdownCloseablePermits() - { - return 3; + private EventBindings( EventHandler<Event> eventHandler, CountdownCloseable countdown ) + { + this.eventHandler = eventHandler; + this.countdown = countdown; + } + + void bindEventHandler( AsynchronousSocketChannel source ) + { + ForkNodeArguments args = getArguments(); + String threadName = "fork-" + args.getForkChannelId() + "-event-thread"; + ReadableByteChannel channel = newBufferedChannel( newInputStream( source ) ); + eventHandlerBindings = new EventConsumerThread( threadName, channel, eventHandler, countdown, args ); + eventHandlerBindings.start(); + } } - @Override - public CloseableDaemonThread bindCommandReader( @Nonnull CommandReader commands, - WritableByteChannel stdIn ) + private class CommandBindings { - // dont use newBufferedChannel here - may cause the command is not sent and the JVM hangs - // only newChannel flushes the message - // newBufferedChannel does not flush - WritableByteChannel channel = newChannel( newOutputStream( worker ) ); - return new StreamFeeder( "commands-fork-" + getArguments().getForkChannelId(), channel, commands, - getArguments().getConsoleLogger() ); + private final CommandReader commands; + + private CommandBindings( CommandReader commands ) + { + this.commands = commands; + } + + void bindCommandSender( AsynchronousSocketChannel source ) + { + // don't use newBufferedChannel here - may cause the command is not sent and the JVM hangs + // only newChannel flushes the message + // newBufferedChannel does not flush + ForkNodeArguments args = getArguments(); + WritableByteChannel channel = newChannel( newOutputStream( source ) ); + String threadName = "commands-fork-" + args.getForkChannelId(); + commandReaderBindings = new StreamFeeder( threadName, channel, commands, args.getConsoleLogger() ); + commandReaderBindings.start(); + } } - @Override - public CloseableDaemonThread bindEventHandler( @Nonnull EventHandler<Event> eventHandler, - @Nonnull CountdownCloseable countdownCloseable, - ReadableByteChannel stdOut ) + private class Bindings extends CountDownLauncher { - out = new LineConsumerThread( "fork-" + getArguments().getForkChannelId() + "-out-thread", stdOut, - new NativeStdOutStreamConsumer( getArguments().getConsoleLogger() ), countdownCloseable ); - out.start(); + private Bindings( int count ) + { + super( count ); + } - ReadableByteChannel channel = newBufferedChannel( newInputStream( worker ) ); - return new EventConsumerThread( "fork-" + getArguments().getForkChannelId() + "-event-thread", channel, - eventHandler, countdownCloseable, getArguments() ); + @Override + protected void job() throws IOException, InterruptedException + { + AsynchronousSocketChannel channel = getChannel(); + if ( isNotBlank( sessionId ) ) + { + verifySessionId(); + } + eventBindings.bindEventHandler( channel ); + commandBindings.bindCommandSender( channel ); + } } - @Override - public void close() throws IOException + private AsynchronousSocketChannel getChannel() + throws InterruptedException, IOException { - //noinspection unused,EmptyTryBlock,EmptyTryBlock - try ( Closeable c1 = worker; Closeable c2 = server; Closeable c3 = out ) + try { - // only close all channels + return session == null ? null : session.get(); + } + catch ( ExecutionException e ) + { + Throwable cause = e.getCause(); + throw cause instanceof IOException ? (IOException) cause : new IOException( cause ); } } } diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java index 87bcca7..a9fde90 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java @@ -22,12 +22,14 @@ package org.apache.maven.plugin.surefire.extensions; import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer; import org.apache.maven.plugin.surefire.log.api.ConsoleLogger; import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger; -import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder; +import org.apache.maven.surefire.api.booter.Command; import org.apache.maven.surefire.api.event.Event; +import org.apache.maven.surefire.api.fork.ForkNodeArguments; import org.apache.maven.surefire.api.report.ConsoleOutputReceiver; +import org.apache.maven.surefire.booter.spi.EventChannelEncoder; import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory; +import org.apache.maven.surefire.extensions.CommandReader; import org.apache.maven.surefire.extensions.EventHandler; -import org.apache.maven.surefire.api.fork.ForkNodeArguments; import org.apache.maven.surefire.extensions.util.CountdownCloseable; import org.junit.Rule; import org.junit.Test; @@ -36,21 +38,22 @@ import org.junit.rules.ExpectedException; import javax.annotation.Nonnull; import java.io.Closeable; import java.io.File; +import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.SECONDS; import static org.fest.assertions.Assertions.assertThat; import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; /** * Simulates the End To End use case where Maven process and Surefire process communicate using the TCP/IP protocol. @@ -68,22 +71,63 @@ public class E2ETest public void endToEndTest() throws Exception { ForkNodeArguments arguments = new Arguments( UUID.randomUUID().toString(), 1, new NullConsoleLogger() ); + final SurefireForkChannel server = new SurefireForkChannel( arguments ); + server.tryConnectToClient(); final String connection = server.getForkNodeConnectionString(); final SurefireMasterProcessChannelProcessorFactory factory = new SurefireMasterProcessChannelProcessorFactory(); factory.connect( connection ); - final MasterProcessChannelEncoder encoder = factory.createEncoder( arguments ); - - System.gc(); - - TimeUnit.SECONDS.sleep( 3L ); + final EventChannelEncoder encoder = (EventChannelEncoder) factory.createEncoder( arguments ); final CountDownLatch awaitHandlerFinished = new CountDownLatch( 2 ); + final AtomicLong readTime = new AtomicLong(); + final int totalCalls = 400_000; // 400_000; // 1_000_000; // 10_000_000; + EventHandler<Event> h = new EventHandler<Event>() + { + private final AtomicInteger counter = new AtomicInteger(); + private volatile long t1; + + @Override + public void handleEvent( @Nonnull Event event ) + { + try + { + if ( counter.getAndIncrement() == 0 ) + { + t1 = System.currentTimeMillis(); + } + + long t2 = System.currentTimeMillis(); + long spent = t2 - t1; + + if ( counter.get() == totalCalls - 64 * 1024 ) + { + readTime.set( spent ); + System.out.println( spent + "ms on read" ); + awaitHandlerFinished.countDown(); + } + } + catch ( Exception e ) + { + e.printStackTrace(); + } + } + }; + + EventHandler<Event> queue = new ThreadedStreamConsumer( h ); + + System.gc(); + + SECONDS.sleep( 5L ); + + server.bindEventHandler( queue, new CountdownCloseable( new DummyCloseable(), 1 ), new DummyReadableChannel() ); + server.bindCommandReader( new DummyCommandReader(), null ); + Thread t = new Thread() { @Override @@ -127,58 +171,12 @@ public class E2ETest t.setDaemon( true ); t.start(); - server.connectToClient(); - - final AtomicLong readTime = new AtomicLong(); - - EventHandler<Event> h = new EventHandler<Event>() - { - private final AtomicInteger counter = new AtomicInteger(); - private volatile long t1; - - @Override - public void handleEvent( @Nonnull Event event ) - { - try - { - if ( counter.getAndIncrement() == 0 ) - { - t1 = System.currentTimeMillis(); - } - - long t2 = System.currentTimeMillis(); - long spent = t2 - t1; - - if ( counter.get() % 500_000 == 0 ) - { - System.out.println( spent + "ms: " + counter.get() ); - } - - if ( counter.get() == totalCalls - 64 * 1024 ) - { - readTime.set( spent ); - System.out.println( spent + "ms on read" ); - awaitHandlerFinished.countDown(); - } - } - catch ( Exception e ) - { - e.printStackTrace(); - } - } - }; - - ThreadedStreamConsumer queue = new ThreadedStreamConsumer( h ); - - server.bindEventHandler( queue, new CountdownCloseable( new DummyCloseable(), 1 ), new DummyReadableChannel() ) - .start(); - - assertThat( awaitHandlerFinished.await( 30L, TimeUnit.SECONDS ) ) + assertThat( awaitHandlerFinished.await( 30L, SECONDS ) ) .isTrue(); factory.close(); server.close(); - queue.close(); + //queue.close(); // 1.0 seconds while using the encoder/decoder assertThat( readTime.get() ) @@ -191,8 +189,8 @@ public class E2ETest @Test( timeout = 10_000L ) public void shouldVerifyClient() throws Exception { - ForkNodeArguments forkNodeArguments = mock( ForkNodeArguments.class ); - when( forkNodeArguments.getSessionId() ).thenReturn( UUID.randomUUID().toString() ); + ForkNodeArguments forkNodeArguments = + new Arguments( UUID.randomUUID().toString(), 1, new NullConsoleLogger() ); try ( SurefireForkChannel server = new SurefireForkChannel( forkNodeArguments ); SurefireMasterProcessChannelProcessorFactory client = new SurefireMasterProcessChannelProcessorFactory() ) @@ -211,8 +209,6 @@ public class E2ETest t.setDaemon( true ); t.start(); - server.connectToClient(); - assertThat( task.get() ) .isEqualTo( "client connected" ); } @@ -221,9 +217,8 @@ public class E2ETest @Test( timeout = 10_000L ) public void shouldNotVerifyClient() throws Exception { - ForkNodeArguments forkNodeArguments = mock( ForkNodeArguments.class ); - String serverSessionId = UUID.randomUUID().toString(); - when( forkNodeArguments.getSessionId() ).thenReturn( serverSessionId ); + ForkNodeArguments forkNodeArguments = + new Arguments( UUID.randomUUID().toString(), 1, new NullConsoleLogger() ); try ( SurefireForkChannel server = new SurefireForkChannel( forkNodeArguments ); SurefireMasterProcessChannelProcessorFactory client = new SurefireMasterProcessChannelProcessorFactory() ) @@ -246,31 +241,133 @@ public class E2ETest e.expect( InvalidSessionIdException.class ); e.expectMessage( "The actual sessionId '6ba7b812-9dad-11d1-80b4-00c04fd430c8' does not match '" - + serverSessionId + "'." ); + + forkNodeArguments.getSessionId() + "'." ); + + server.tryConnectToClient(); + server.bindCommandReader( new DummyCommandReader(), new DummyWritableByteChannel() ); - server.connectToClient(); + server.bindEventHandler( new DummyEventHandler(), + new CountdownCloseable( new DummyCloseable(), 1 ), new DummyReadableChannel() ); fail( task.get() ); } } + private static class DummyEventHandler<Event> implements EventHandler<Event> + { + @Override + public void handleEvent( @Nonnull Event event ) + { + } + } + private static class DummyReadableChannel implements ReadableByteChannel { + private volatile Thread t; + @Override - public int read( ByteBuffer dst ) + public int read( ByteBuffer dst ) throws IOException { - return 0; + try + { + t = Thread.currentThread(); + HOURS.sleep( 1L ); + return 0; + } + catch ( InterruptedException e ) + { + throw new IOException( e.getLocalizedMessage(), e ); + } } @Override public boolean isOpen() { - return false; + return true; } @Override public void close() { + if ( t != null ) + { + t.interrupt(); + } + } + } + + private static class DummyWritableByteChannel implements WritableByteChannel + { + private volatile Thread t; + + @Override + public int write( ByteBuffer src ) throws IOException + { + try + { + t = Thread.currentThread(); + HOURS.sleep( 1L ); + return 0; + } + catch ( InterruptedException e ) + { + throw new IOException( e.getLocalizedMessage(), e ); + } + } + + @Override + public boolean isOpen() + { + return true; + } + + @Override + public void close() throws IOException + { + if ( t != null ) + { + t.interrupt(); + } + } + } + + private static class DummyCommandReader implements CommandReader + { + private volatile Thread t; + + @Override + public Command readNextCommand() throws IOException + { + try + { + t = Thread.currentThread(); + HOURS.sleep( 1L ); + return null; + } + catch ( InterruptedException e ) + { + throw new IOException( e.getLocalizedMessage(), e ); + } + } + + @Override + public void close() + { + if ( t != null ) + { + t.interrupt(); + } + } + + @Override + public boolean isClosed() + { + return false; + } + + @Override + public void tryFlush() + { } } diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java index 428ab10..9655c63 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java @@ -157,11 +157,11 @@ public class ForkChannelTest Client client = new Client( uri.getPort(), sessionId ); client.start(); - channel.connectToClient(); - channel.bindCommandReader( commandReader, null ).start(); + channel.tryConnectToClient(); + channel.bindCommandReader( commandReader, null ); ReadableByteChannel stdOut = mock( ReadableByteChannel.class ); when( stdOut.read( any( ByteBuffer.class ) ) ).thenReturn( -1 ); - channel.bindEventHandler( consumer, cc, stdOut ).start(); + channel.bindEventHandler( consumer, cc, stdOut ); commandReader.noop(); diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/util/internal/AbstractNoninterruptibleReadableChannel.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/util/internal/AbstractNoninterruptibleReadableChannel.java index 678d643..2b83e0f 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/api/util/internal/AbstractNoninterruptibleReadableChannel.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/util/internal/AbstractNoninterruptibleReadableChannel.java @@ -63,7 +63,13 @@ abstract class AbstractNoninterruptibleReadableChannel implements ReadableByteCh @Override public final void close() throws IOException { - open = false; - closeImpl(); + try + { + closeImpl(); + } + finally + { + open = false; + } } } diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java index cc2964c..f0f32d3 100644 --- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java +++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java @@ -20,18 +20,15 @@ package org.apache.maven.surefire.extensions; */ import javax.annotation.Nonnull; -import java.io.Closeable; /** * The base thread class used to handle a stream, transforms the stream to an object. */ -public abstract class CloseableDaemonThread extends Thread implements Closeable +public abstract class CloseableDaemonThread extends Thread implements Stoppable { protected CloseableDaemonThread( @Nonnull String threadName ) { setName( threadName ); setDaemon( true ); } - - public abstract void disable(); } diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/Completable.java similarity index 64% copy from surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java copy to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/Completable.java index cc2964c..30506b2 100644 --- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java +++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/Completable.java @@ -19,19 +19,24 @@ package org.apache.maven.surefire.extensions; * under the License. */ -import javax.annotation.Nonnull; -import java.io.Closeable; +import java.io.IOException; /** - * The base thread class used to handle a stream, transforms the stream to an object. + * Forks the execution of task and the task completion. + * The method {@link #complete()} waits for the task to complete or fails. + * + * @author <a href="mailto:[email protected]">Tibor Digana (tibor17)</a> + * @since 3.0.0-M5 */ -public abstract class CloseableDaemonThread extends Thread implements Closeable +public interface Completable { - protected CloseableDaemonThread( @Nonnull String threadName ) + Completable EMPTY_COMPLETABLE = new Completable() { - setName( threadName ); - setDaemon( true ); - } + @Override + public void complete() + { + } + }; - public abstract void disable(); + void complete() throws IOException, InterruptedException; } diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkChannel.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkChannel.java index 62e88be..38d3648 100644 --- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkChannel.java +++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkChannel.java @@ -34,8 +34,9 @@ import java.nio.channels.WritableByteChannel; * and communicates with a dedicated forked JVM. It represents a server. * <br> * <br> - * It connects to a remote client by {@link #connectToClient()}, provides a connection string + * It connects to a remote client by {@link #tryConnectToClient()}, provides a connection string * {@link #getForkNodeConnectionString()} needed by the client in forked JVM, binds event handler and command reader. + * This object is called in one Thread. * * @author <a href="mailto:[email protected]">Tibor Digana (tibor17)</a> * @since 3.0.0-M5 @@ -53,7 +54,13 @@ public abstract class ForkChannel implements Closeable this.arguments = arguments; } - public abstract void connectToClient() throws IOException; + /** + * Asynchronously connects to the client. + * + * @throws IOException if stream fails + * @throws InterruptedException if interrupted thread + */ + public abstract void tryConnectToClient() throws IOException, InterruptedException; /** * This is server related class, which if binds to a TCP port, determines the connection string for the client. @@ -68,33 +75,38 @@ public abstract class ForkChannel implements Closeable public abstract int getCountdownCloseablePermits(); /** - * Binds command handler to the channel. + * Binds command handler to the channel. Starts a Thread streaming out the commands. * * @param commands command reader, see {@link CommandReader#readNextCommand()} * @param stdIn optional standard input stream of the JVM to write the encoded commands into it - * @return the thread instance to start up in order to stream out the data * @throws IOException if an error in the fork channel + * @throws InterruptedException channel interrupted */ - public abstract CloseableDaemonThread bindCommandReader( @Nonnull CommandReader commands, - WritableByteChannel stdIn ) - throws IOException; + public abstract void bindCommandReader( @Nonnull CommandReader commands, WritableByteChannel stdIn ) + throws IOException, InterruptedException; /** + * Starts a Thread reading the events. * * @param eventHandler event eventHandler * @param countdownCloseable count down of the final call of {@link Closeable#close()} * @param stdOut optional standard output stream of the JVM - * @return the thread instance to start up in order to stream out the data * @throws IOException if an error in the fork channel + * @throws InterruptedException channel interrupted */ - public abstract CloseableDaemonThread bindEventHandler( @Nonnull EventHandler<Event> eventHandler, - @Nonnull CountdownCloseable countdownCloseable, - ReadableByteChannel stdOut ) - throws IOException; + public abstract void bindEventHandler( @Nonnull EventHandler<Event> eventHandler, + @Nonnull CountdownCloseable countdownCloseable, + ReadableByteChannel stdOut ) + throws IOException, InterruptedException; @Nonnull protected ForkNodeArguments getArguments() { return arguments; } + + public abstract void disable(); + + @Override + public abstract void close() throws IOException; } diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/Stoppable.java similarity index 70% copy from surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java copy to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/Stoppable.java index cc2964c..e637b3e 100644 --- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java +++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/Stoppable.java @@ -19,19 +19,12 @@ package org.apache.maven.surefire.extensions; * under the License. */ -import javax.annotation.Nonnull; import java.io.Closeable; /** - * The base thread class used to handle a stream, transforms the stream to an object. + * Used in {@link CloseableDaemonThread}. */ -public abstract class CloseableDaemonThread extends Thread implements Closeable +public interface Stoppable extends Closeable { - protected CloseableDaemonThread( @Nonnull String threadName ) - { - setName( threadName ); - setDaemon( true ); - } - - public abstract void disable(); + void disable(); } diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/InvalidSessionIdException.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CountDownLauncher.java similarity index 51% copy from maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/InvalidSessionIdException.java copy to surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CountDownLauncher.java index 2ceaf12..6d0e443 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/InvalidSessionIdException.java +++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CountDownLauncher.java @@ -1,4 +1,4 @@ -package org.apache.maven.plugin.surefire.extensions; +package org.apache.maven.surefire.extensions.util; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -19,21 +19,36 @@ package org.apache.maven.plugin.surefire.extensions; * under the License. */ -import org.apache.maven.surefire.extensions.ForkChannel; -import org.apache.maven.surefire.extensions.util.CommandlineExecutor; - import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; /** - * After the authentication has failed, {@link ForkChannel#connectToClient()} throws {@link InvalidSessionIdException} - * and {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter} should close {@link CommandlineExecutor}. + * Counts down the calls {@link #countDown()} and the last reaching zero executes the {@link #job()}. * + * @author <a href="mailto:[email protected]">Tibor Digana (tibor17)</a> * @since 3.0.0-M5 */ -public class InvalidSessionIdException extends IOException +public abstract class CountDownLauncher { - public InvalidSessionIdException( String actualSessionId, String expectedSessionId ) + private final AtomicInteger countDown; + + public CountDownLauncher( int count ) + { + if ( count <= 0 ) + { + throw new IllegalStateException( "count=" + count + " should be greater than zero" ); + } + + countDown = new AtomicInteger( count ); + } + + protected abstract void job() throws IOException, InterruptedException; + + public void countDown() throws IOException, InterruptedException { - super( "The actual sessionId '" + actualSessionId + "' does not match '" + expectedSessionId + "'." ); + if ( countDown.decrementAndGet() == 0 ) + { + job(); + } } } diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1881.java similarity index 64% copy from surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java copy to surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1881.java index cc2964c..a2b1ec2 100644 --- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/CloseableDaemonThread.java +++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1881.java @@ -1,4 +1,4 @@ -package org.apache.maven.surefire.extensions; +package org.apache.maven.surefire.its.jiras; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -19,19 +19,21 @@ package org.apache.maven.surefire.extensions; * under the License. */ -import javax.annotation.Nonnull; -import java.io.Closeable; +import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase; +import org.junit.Test; /** - * The base thread class used to handle a stream, transforms the stream to an object. + * */ -public abstract class CloseableDaemonThread extends Thread implements Closeable +@SuppressWarnings( "checkstyle:magicnumber" ) +public class Surefire1881 extends SurefireJUnit4IntegrationTestCase { - protected CloseableDaemonThread( @Nonnull String threadName ) + + @Test( timeout = 30_000L ) + public void test() throws Exception { - setName( threadName ); - setDaemon( true ); + unpack( "/surefire-1881" ) + .executeVerify() + .assertIntegrationTestSuiteResults( 1, 0, 0, 0 ); } - - public abstract void disable(); } diff --git a/surefire-its/src/test/resources/surefire-1881/pom.xml b/surefire-its/src/test/resources/surefire-1881/pom.xml new file mode 100644 index 0000000..c44fca6 --- /dev/null +++ b/surefire-its/src/test/resources/surefire-1881/pom.xml @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.maven.plugins.surefire</groupId> + <artifactId>surefire-1881</artifactId> + <version>1.0-SNAPSHOT</version> + + <properties> + <maven.compiler.source>${java.specification.version}</maven.compiler.source> + <maven.compiler.target>${java.specification.version}</maven.compiler.target> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + </properties> + + <contributors> + <contributor> + <name>Alexander Kriegisch</name> + <roles> + <role>author</role> + </roles> + <url>https://github.com/kriegaex/Maven_Surefire_PrintToConsoleProblems</url> + </contributor> + </contributors> + + <build> + <plugins> + <plugin> + <artifactId>maven-jar-plugin</artifactId> + <version>3.2.0</version> + <configuration> + <forceCreation>true</forceCreation> + <archive> + <manifestEntries> + <Agent-Class>de.scrum_master.dummy.Agent</Agent-Class> + <Premain-Class>de.scrum_master.dummy.Agent</Premain-Class> + <Can-Redefine-Classes>true</Can-Redefine-Classes> + <Can-Retransform-Classes>true</Can-Retransform-Classes> + </manifestEntries> + <addMavenDescriptor>false</addMavenDescriptor> + </archive> + </configuration> + </plugin> + + <plugin> + <artifactId>maven-failsafe-plugin</artifactId> + <version>${surefire.version}</version> + <configuration> + <argLine>-javaagent:${project.build.directory}/${project.build.finalName}.jar</argLine> + + <!-- Fix for "[WARNING] Corrupted STDOUT by directly writing to native stream in forked JVM" --> + <!-- FIXME: + Using this option is meant to fix "[WARNING] Corrupted STDOUT by directly writing to native stream in + forked JVM" and creation of "*-jvmRun1.dumpstream" files. Unfortunately, it makes Surefire/Failsafe + freeze if a Java agent prints something to stdOut or stdErr. This happens both in M5 and in M6-SNAPSHOT + after both SUREFIRE-1788 and SUREFIRE-1809 have been merged in already. + FIXME: + *Not* using this option leads to garbled log output when a Java agent writes to both stdOut and stdErr + before/during tests. See comments in class de.scrum_master.dummy.Agent.DummyTransformer for examples. + TODO: + If the garbled output would also appear with this option activated, cannot be tested at present due to + the Surefire/Failsafe freeze -> re-test after the freeze has been fixed. + --> + <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/> + + <!-- TODO: + We have to use this until SUREFIRE-1809 is fixed in M6, otherwise there are JPMS problems with + libraries added to the boot class path. -> re-test, then remove this option + --> + <useModulePath>false</useModulePath> + + <!-- + <consoleOutputReporter implementation="org.apache.maven.plugin.surefire.extensions.SurefireConsoleOutputReporter"> + <!– Suppress Surefire/Failsafe output in favour of test output only –> + <disable>true</disable> + <encoding>UTF-8</encoding> + </consoleOutputReporter> + --> + + <!-- + <enableProcessChecker>ping</enableProcessChecker> + <forkedProcessTimeoutInSeconds>15</forkedProcessTimeoutInSeconds> + --> + </configuration> + <executions> + <execution> + <id>integration-test</id> + <goals> + <goal>integration-test</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.13.1</version> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/surefire-its/src/test/resources/surefire-1881/src/main/java/de/scrum_master/dummy/Agent.java b/surefire-its/src/test/resources/surefire-1881/src/main/java/de/scrum_master/dummy/Agent.java new file mode 100644 index 0000000..3a0c68d --- /dev/null +++ b/surefire-its/src/test/resources/surefire-1881/src/main/java/de/scrum_master/dummy/Agent.java @@ -0,0 +1,49 @@ +package de.scrum_master.dummy; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; + +public class Agent +{ + public static Instrumentation INSTRUMENTATION; + + public static void premain( String commandLineOptions, Instrumentation instrumentation ) + { + INSTRUMENTATION = instrumentation; + // These console outputs still work in Surefire/Failsafe freeze when using option + // <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/> + System.out.println( "[Agent OUT] Hello world!" ); + System.err.println( "[Agent ERR] Uh-oh!" ); + instrumentation.addTransformer( new DummyTransformer(), true ); + } + + public static class DummyTransformer implements ClassFileTransformer + { + @Override + public byte[] transform( ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer ) + { + /* + FIXME: These console outputs make Surefire/Failsafe freeze when using option + <forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/> + + FIXME: But when *not* using that option, they mess up console output like this (some lines in between deleted): + [Transformer OUT] cl[Transformer ERR] className = sun/reflect/generics/tree/TypeArgument, class file size = 173 + assName = sun/reflect/generics/tree/SimpleClassTypeSignature, loader = null + [Transformer OUT] className = java/lang/annotation/Retention[Transformer ERR] className = sun/reflect/generics/visitor/Reifier, class file size = 8341 + Policy, loader = null + [Transformer OUT] className = org/junit/runner/notification/SynchronizedRunListener, loader[Transformer ERR] className = com/sun/proxy/$Proxy0, class file size = 2268 + = jdk.internal.loader.ClassLoaders$AppClassLoader@5451c3a8 + [T[Transformer ERR] className = org/junit/runners/BlockJUnit4ClassRunner, class file size = 14072 + ransformer OUT] className = org/junit/runners/ParentRunner, loader = jdk.internal.loader.ClassLoaders$AppClassLoader@5451c3a8 + Transformer ERR] className = org/junit/validator/AnnotationsValidator$FieldValidator, class file size = 2179 + [[Transformer OUT] className = org/junit/validator/AnnotationsValidator$FieldValidator, loader = jdk.internal.loader.ClassLoaders$AppClassLoader@5451c3a8 + */ + System.out.println( "[Transformer OUT] className = " + className + ", loader = " + loader ); + System.err.println( "[Transformer ERR] className = " + className + ", class file size = " + + classfileBuffer.length ); + return null; + } + } +} + diff --git a/surefire-its/src/test/resources/surefire-1881/src/test/java/de/scrum_master/dummy/AgentIT.java b/surefire-its/src/test/resources/surefire-1881/src/test/java/de/scrum_master/dummy/AgentIT.java new file mode 100644 index 0000000..58ee14f --- /dev/null +++ b/surefire-its/src/test/resources/surefire-1881/src/test/java/de/scrum_master/dummy/AgentIT.java @@ -0,0 +1,23 @@ +package de.scrum_master.dummy; + +import org.junit.Test; + +import java.io.PrintStream; +import java.net.URL; + +public class AgentIT +{ + @Test + public void test() throws Exception + { + System.out.println( "[Test OUT] Before manual JRE bootstrap class retransformation" ); + System.err.println( "[Test ERR] Before manual JRE bootstrap class retransformation" ); + // Just for fun, manually apply dummy class transformation to some JRE bootstrap classes. + // This is unrelated to the Surefire issue, but produces a few more log lines with + // 'loader = null', signifying the boot loader. + Agent.INSTRUMENTATION.retransformClasses( AgentIT.class, String.class, URL.class, PrintStream.class, + Integer.class ); + System.out.println( "[Test OUT] After manual JRE bootstrap class retransformation" ); + System.err.println( "[Test ERR] After manual JRE bootstrap class retransformation" ); + } +}
