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 2020/08/25 17:26:25 UTC
[maven-surefire] 01/01: 300 - 400 nanos per decoding std-out (100
chars/line)
This is an automated email from the ASF dual-hosted git repository.
tibordigana pushed a commit to branch base64
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git
commit 390cf0b294bf4a94fe2fb1639d7c643098e56bfa
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Aug 1 23:09:34 2020 +0200
300 - 400 nanos per decoding std-out (100 chars/line)
---
.../surefire/extensions/EventConsumerThread.java | 805 +++++++++++++++------
.../extensions/EventConsumerThreadTest.java | 492 +++++++++++++
.../api/booter/ForkedProcessEventType.java | 230 +++++-
3 files changed, 1300 insertions(+), 227 deletions(-)
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
index 1d5686b..b74cb5e 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
@@ -44,28 +44,37 @@ import org.apache.maven.surefire.api.event.TestStartingEvent;
import org.apache.maven.surefire.api.event.TestSucceededEvent;
import org.apache.maven.surefire.api.event.TestsetCompletedEvent;
import org.apache.maven.surefire.api.event.TestsetStartingEvent;
+import org.apache.maven.surefire.api.report.RunMode;
+import org.apache.maven.surefire.api.report.StackTraceWriter;
+import org.apache.maven.surefire.api.report.TestSetReportEntry;
import org.apache.maven.surefire.extensions.CloseableDaemonThread;
import org.apache.maven.surefire.extensions.EventHandler;
import org.apache.maven.surefire.extensions.ForkNodeArguments;
import org.apache.maven.surefire.extensions.util.CountdownCloseable;
-import org.apache.maven.surefire.api.report.RunMode;
-import org.apache.maven.surefire.api.report.StackTraceWriter;
-import org.apache.maven.surefire.api.report.TestSetReportEntry;
-import org.apache.maven.surefire.shared.codec.binary.Base64;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
-import java.util.Iterator;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import static java.lang.Math.max;
+import static java.nio.charset.CodingErrorAction.REPLACE;
import static java.nio.charset.StandardCharsets.US_ASCII;
-import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.MAGIC_NUMBER;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.StreamReadStatus.EOF;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.StreamReadStatus.OVERFLOW;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThread.StreamReadStatus.UNDERFLOW;
+import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER;
+import static org.apache.maven.surefire.api.booter.Constants.STREAM_ENCODING;
import static org.apache.maven.surefire.api.report.CategorizedReportEntry.reportEntry;
import static org.apache.maven.surefire.api.report.RunMode.MODES;
@@ -83,7 +92,10 @@ public class EventConsumerThread extends CloseableDaemonThread
"java.lang.module.findexception" // JPMS errors
};
private static final String PRINTABLE_JVM_NATIVE_STREAM = "Listening for transport dt_socket at address:";
- private static final Base64 BASE64 = new Base64();
+ private static final byte[] MAGIC_NUMBER_BYTES = MAGIC_NUMBER.getBytes( US_ASCII );
+ private static final int DELIMINATOR_LENGTH = 1;
+ private static final int BYTE_LENGTH = 1;
+ private static final int INT_LENGTH = 4;
private final ReadableByteChannel channel;
private final EventHandler<Event> eventHandler;
@@ -133,153 +145,307 @@ public class EventConsumerThread extends CloseableDaemonThread
@SuppressWarnings( "checkstyle:innerassignment" )
private void decode() throws IOException
{
- List<String> tokens = new ArrayList<>();
- StringBuilder line = new StringBuilder();
- StringBuilder token = new StringBuilder( MAGIC_NUMBER.length() );
- ByteBuffer buffer = ByteBuffer.allocate( 1024 );
- buffer.position( buffer.limit() );
- boolean streamContinues;
+ Map<Segment, ForkedProcessEventType> events = mapEventTypes();
+ Memento memento = new Memento();
+ BufferedStream line = new BufferedStream( 32 );
+ memento.bb.limit( 0 );
+ boolean streamContinues = true;
start:
do
{
- line.setLength( 0 );
- tokens.clear();
- token.setLength( 0 );
- FrameCompletion completion = null;
- for ( boolean frameStarted = false; streamContinues = read( buffer ); completion = null )
+ if ( !streamContinues )
{
- char c = (char) buffer.get();
+ printExistingLine( line );
+ return;
+ }
- if ( c == '\n' || c == '\r' )
- {
- printExistingLine( line );
- continue start;
- }
+ line.reset();
+ memento.segment.reset();
- line.append( c );
+ int readCount =
+ DELIMINATOR_LENGTH + MAGIC_NUMBER_BYTES.length + DELIMINATOR_LENGTH + BYTE_LENGTH + DELIMINATOR_LENGTH;
+ streamContinues = read( memento.bb, readCount ) != EOF;
+ if ( memento.bb.remaining() < readCount )
+ {
+ //todo throw exception - broken stream
+ }
+ checkHeader( memento.bb, memento.segment );
- if ( !frameStarted )
+ memento.eventType = events.get( readSegment( memento.bb ) );
+
+ for ( SegmentType segmentType : nextSegmentType( memento ) )
+ {
+ if ( segmentType == null )
{
- if ( c == ':' )
- {
- frameStarted = true;
- token.setLength( 0 );
- tokens.clear();
- }
+ break;
}
- else
+
+ switch ( segmentType )
{
- if ( c == ':' )
- {
- tokens.add( token.toString() );
- token.setLength( 0 );
- completion = frameCompleteness( tokens );
- if ( completion == FrameCompletion.COMPLETE )
+ case RUN_MODE:
+ memento.runMode = MODES.get( readSegment( memento ) );
+ break;
+ case STRING_ENCODING:
+ //todo handle exceptions
+ memento.charset = Charset.forName( readSegment( memento ) );
+ break;
+ case BYTES_INT_COUNTER:
+ memento.bytesCounter = readInt( memento.bb );
+ break;
+ case DATA_STRING:
+ memento.cb.clear();
+ int bytesCounter = memento.bytesCounter;
+ if ( bytesCounter == 0 )
+ {
+ memento.data.add( "" );
+ }
+ else if ( bytesCounter == 1 )
{
- line.setLength( 0 );
- break;
+ // handle the returned boolean
+ read( memento.bb, 1 );
+ byte oneChar = memento.bb.get();
+ memento.data.add( oneChar == 0 ? null : String.valueOf( (char) oneChar ) );
}
- else if ( completion == FrameCompletion.MALFORMED )
+ else
{
- printExistingLine( line );
- continue start;
+ memento.data.add( readString( memento ) );
}
- }
- else
- {
- token.append( c );
- }
+ memento.bytesCounter = 0;
+ break;
+ case DATA_INT:
+ memento.data.add( readInt( memento.bb ) );
+ break;
+ case END_OF_FRAME:
+ eventHandler.handleEvent( toEvent( memento ) );
+ continue start;
+ default:
+ throw new IllegalArgumentException( "Unknown enum " + segmentType );
}
- }
- if ( completion == FrameCompletion.COMPLETE )
- {
- Event event = toEvent( tokens );
- if ( !disabled && event != null )
+ memento.cb.clear();
+
+ read( memento.bb, 1 );
+ b = 0xff & memento.bb.get();
+ if ( Character.isDefined( b ) && b != ':' )
{
- eventHandler.handleEvent( event );
+ //memento.segmentCompletion = SegmentCompletion.;
+ //MalformedStreamException
+ continue start;
}
}
- if ( !streamContinues )
+ memento.bb.flip();
+ line.write( memento.bb );
+ memento.reset();
+ }
+ while ( true );
+ }
+
+ @Nonnull
+ private static Segment readSegment( ByteBuffer bb )
+ {
+ int readCount = bb.get() & 0xff;
+ Segment segment = new Segment( bb.array(), bb.arrayOffset() + bb.position(), readCount );
+ bb.position( bb.position() + readCount );
+ checkDelimiter( bb );
+ return segment;
+ }
+
+ private static void checkHeader( ByteBuffer bb, BufferedStream segment )
+ {
+ checkDelimiter( bb );
+
+ segment.reset();
+
+ int shift = 0;
+ for ( int start = bb.arrayOffset() + bb.position(), length = MAGIC_NUMBER_BYTES.length;
+ shift < length; shift++ )
+ {
+ if ( bb.array()[shift + start] != MAGIC_NUMBER_BYTES[shift] )
{
- printExistingLine( line );
- return;
+ //todo throw exception - broken stream
}
}
- while ( true );
+ bb.position( bb.position() + shift );
+
+ checkDelimiter( bb );
}
- private boolean read( ByteBuffer buffer ) throws IOException
+ private static void checkDelimiter( ByteBuffer bb )
{
- if ( buffer.hasRemaining() && buffer.position() > 0 )
+ if ( ( 0xff & bb.get() ) != ':' )
{
- return true;
+ //todo throw exception - broken stream
}
- else
+ }
+
+ private static SegmentType[] nextSegmentType( Memento memento )
+ {
+ switch ( memento.eventType )
{
- buffer.clear();
- boolean isEndOfStream = channel.read( buffer ) == -1;
- buffer.flip();
- return !isEndOfStream;
+ case BOOTERCODE_BYE:
+ case BOOTERCODE_STOP_ON_NEXT_TEST:
+ case BOOTERCODE_NEXT_TEST:
+ return new SegmentType[] {SegmentType.END_OF_FRAME};
+ case BOOTERCODE_CONSOLE_ERROR:
+ case BOOTERCODE_JVM_EXIT_ERROR:
+ return new SegmentType[] {
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.STRING_ENCODING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.END_OF_FRAME
+ };
+ case BOOTERCODE_CONSOLE_INFO:
+ case BOOTERCODE_CONSOLE_DEBUG:
+ case BOOTERCODE_CONSOLE_WARNING:
+ return new SegmentType[] {
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.STRING_ENCODING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.END_OF_FRAME
+ };
+ case BOOTERCODE_STDOUT:
+ case BOOTERCODE_STDOUT_NEW_LINE:
+ case BOOTERCODE_STDERR:
+ case BOOTERCODE_STDERR_NEW_LINE:
+ return new SegmentType[] {
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.RUN_MODE,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.STRING_ENCODING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.END_OF_FRAME
+ };
+ case BOOTERCODE_SYSPROPS:
+ return new SegmentType[] {
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.RUN_MODE,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.STRING_ENCODING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.END_OF_FRAME
+ };
+ case BOOTERCODE_TESTSET_STARTING:
+ case BOOTERCODE_TESTSET_COMPLETED:
+ case BOOTERCODE_TEST_STARTING:
+ case BOOTERCODE_TEST_SUCCEEDED:
+ case BOOTERCODE_TEST_FAILED:
+ case BOOTERCODE_TEST_SKIPPED:
+ case BOOTERCODE_TEST_ERROR:
+ case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
+ return new SegmentType[] {
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.RUN_MODE,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.STRING_ENCODING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.DATA_INT,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.BYTES_INT_COUNTER,
+ SegmentType.DATA_STRING,
+ SegmentType.END_OF_FRAME
+ };
+ default:
+ throw new IllegalArgumentException( "Unknown enum " + memento.eventType );
}
}
- private void printExistingLine( StringBuilder line )
+ private StreamReadStatus read( ByteBuffer buffer, int recommendedCount ) throws IOException
{
- if ( line.length() != 0 )
+ if ( buffer.remaining() >= recommendedCount && buffer.position() != 0 )
+ {
+ return OVERFLOW;
+ }
+ else
{
- ConsoleLogger logger = arguments.getConsoleLogger();
- String s = line.toString().trim();
- if ( s.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
+ if ( buffer.position() != 0 && recommendedCount > buffer.capacity() - buffer.position() )
{
- if ( logger.isDebugEnabled() )
- {
- logger.debug( s );
- }
- else if ( logger.isInfoEnabled() )
- {
- logger.info( s );
- }
- else
- {
- // In case of debugging forked JVM, see PRINTABLE_JVM_NATIVE_STREAM.
- System.out.println( s );
- }
+ buffer.compact().flip();
}
- else
+ int mark = buffer.position();
+ buffer.position( buffer.limit() );
+ buffer.limit( buffer.capacity() );
+ boolean isEnd = false;
+ while ( !isEnd && buffer.position() - mark < recommendedCount && buffer.position() != buffer.limit() )
{
- if ( isJvmError( s ) )
- {
- logger.error( s );
- }
- String msg = "Corrupted STDOUT by directly writing to native stream in forked JVM "
- + arguments.getForkChannelId() + ".";
- File dumpFile = arguments.dumpStreamText( msg + " Stream '" + s + "'." );
- arguments.logWarningAtEnd( msg + " See FAQ web page and the dump file " + dumpFile.getAbsolutePath() );
-
- if ( logger.isDebugEnabled() )
- {
- logger.debug( s );
- }
+ isEnd = channel.read( buffer ) == -1;
}
+ buffer.limit( buffer.position() );
+ buffer.position( mark );
+ return isEnd ? EOF : ( buffer.remaining() >= recommendedCount ? OVERFLOW : UNDERFLOW );
}
}
- private Event toEvent( List<String> tokensInFrame )
+ private void printExistingLine( BufferedStream line )
{
- Iterator<String> tokens = tokensInFrame.iterator();
- String header = tokens.next();
- assert header != null;
-
- ForkedProcessEventType event = ForkedProcessEventType.byOpcode( tokens.next() );
-
- if ( event == null )
+ if ( line.isEmpty() )
+ {
+ return;
+ }
+ ConsoleLogger logger = arguments.getConsoleLogger();
+ String s = line.toString( STREAM_ENCODING ).trim();
+ if ( s.contains( PRINTABLE_JVM_NATIVE_STREAM ) )
+ {
+ if ( logger.isDebugEnabled() )
+ {
+ logger.debug( s );
+ }
+ else if ( logger.isInfoEnabled() )
+ {
+ logger.info( s );
+ }
+ else
+ {
+ // In case of debugging forked JVM, see PRINTABLE_JVM_NATIVE_STREAM.
+ System.out.println( s );
+ }
+ }
+ else
{
- return null;
+ if ( isJvmError( s ) )
+ {
+ logger.error( s );
+ }
+ String msg = "Corrupted STDOUT by directly writing to native stream in forked JVM "
+ + arguments.getForkChannelId() + ".";
+ File dumpFile = arguments.dumpStreamText( msg + " Stream '" + s + "'." );
+ arguments.logWarningAtEnd( msg + " See FAQ web page and the dump file " + dumpFile.getAbsolutePath() );
+
+ if ( logger.isDebugEnabled() )
+ {
+ logger.debug( s );
+ }
}
+ }
+ private Event toEvent( Memento memento )
+ {
+ ForkedProcessEventType event = memento.eventType;
if ( event.isControlCategory() )
{
switch ( event )
@@ -296,16 +462,17 @@ public class EventConsumerThread extends CloseableDaemonThread
}
else if ( event.isConsoleErrorCategory() || event.isJvmExitError() )
{
- Charset encoding = Charset.forName( tokens.next() );
- StackTraceWriter stackTraceWriter = decodeTrace( encoding, tokens.next(), tokens.next(), tokens.next() );
+ String traceMessage = (String) memento.data.get( 0 );
+ String smartTrimmedStackTrace = (String) memento.data.get( 1 );
+ String stackTrace = (String) memento.data.get( 2 );
+ StackTraceWriter stackTraceWriter = newTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
return event.isConsoleErrorCategory()
? new ConsoleErrorEvent( stackTraceWriter )
: new JvmExitErrorEvent( stackTraceWriter );
}
else if ( event.isConsoleCategory() )
{
- Charset encoding = Charset.forName( tokens.next() );
- String msg = decode( tokens.next(), encoding );
+ String msg = (String) memento.data.get( 0 );
switch ( event )
{
case BOOTERCODE_CONSOLE_INFO:
@@ -320,57 +487,62 @@ public class EventConsumerThread extends CloseableDaemonThread
}
else if ( event.isStandardStreamCategory() )
{
- RunMode mode = MODES.get( tokens.next() );
- Charset encoding = Charset.forName( tokens.next() );
- String output = decode( tokens.next(), encoding );
+ String output = (String) memento.data.get( 0 );
switch ( event )
{
case BOOTERCODE_STDOUT:
- return new StandardStreamOutEvent( mode, output );
+ return new StandardStreamOutEvent( memento.runMode, output );
case BOOTERCODE_STDOUT_NEW_LINE:
- return new StandardStreamOutWithNewLineEvent( mode, output );
+ return new StandardStreamOutWithNewLineEvent( memento.runMode, output );
case BOOTERCODE_STDERR:
- return new StandardStreamErrEvent( mode, output );
+ return new StandardStreamErrEvent( memento.runMode, output );
case BOOTERCODE_STDERR_NEW_LINE:
- return new StandardStreamErrWithNewLineEvent( mode, output );
+ return new StandardStreamErrWithNewLineEvent( memento.runMode, output );
default:
throw new IllegalStateException( "Unknown enum " + event );
}
}
else if ( event.isSysPropCategory() )
{
- RunMode mode = MODES.get( tokens.next() );
- Charset encoding = Charset.forName( tokens.next() );
- String key = decode( tokens.next(), encoding );
- String value = decode( tokens.next(), encoding );
- return new SystemPropertyEvent( mode, key, value );
+ String key = (String) memento.data.get( 0 );
+ String value = (String) memento.data.get( 1 );
+ return new SystemPropertyEvent( memento.runMode, key, value );
}
else if ( event.isTestCategory() )
{
- RunMode mode = MODES.get( tokens.next() );
- Charset encoding = Charset.forName( tokens.next() );
- TestSetReportEntry reportEntry =
- decodeReportEntry( encoding, tokens.next(), tokens.next(), tokens.next(), tokens.next(),
- tokens.next(), tokens.next(), tokens.next(), tokens.next(), tokens.next(), tokens.next() );
+ // ReportEntry:
+ String source = (String) memento.data.get( 0 );
+ String sourceText = (String) memento.data.get( 1 );
+ String name = (String) memento.data.get( 2 );
+ String nameText = (String) memento.data.get( 3 );
+ String group = (String) memento.data.get( 4 );
+ String message = (String) memento.data.get( 5 );
+ Integer timeElapsed = (Integer) memento.data.get( 6 );
+ // StackTraceWriter:
+ String traceMessage = (String) memento.data.get( 7 );
+ String smartTrimmedStackTrace = (String) memento.data.get( 8 );
+ String stackTrace = (String) memento.data.get( 9 );
+ TestSetReportEntry reportEntry = newReportEntry( source, sourceText, name, nameText, group, message,
+ timeElapsed, traceMessage, smartTrimmedStackTrace, stackTrace );
switch ( event )
{
case BOOTERCODE_TESTSET_STARTING:
- return new TestsetStartingEvent( mode, reportEntry );
+ return new TestsetStartingEvent( memento.runMode, reportEntry );
case BOOTERCODE_TESTSET_COMPLETED:
- return new TestsetCompletedEvent( mode, reportEntry );
+ return new TestsetCompletedEvent( memento.runMode, reportEntry );
case BOOTERCODE_TEST_STARTING:
- return new TestStartingEvent( mode, reportEntry );
+ return new TestStartingEvent( memento.runMode, reportEntry );
case BOOTERCODE_TEST_SUCCEEDED:
- return new TestSucceededEvent( mode, reportEntry );
+ return new TestSucceededEvent( memento.runMode, reportEntry );
case BOOTERCODE_TEST_FAILED:
- return new TestFailedEvent( mode, reportEntry );
+ return new TestFailedEvent( memento.runMode, reportEntry );
case BOOTERCODE_TEST_SKIPPED:
- return new TestSkippedEvent( mode, reportEntry );
+ return new TestSkippedEvent( memento.runMode, reportEntry );
case BOOTERCODE_TEST_ERROR:
- return new TestErrorEvent( mode, reportEntry );
+ return new TestErrorEvent( memento.runMode, reportEntry );
case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
- return new TestAssumptionFailureEvent( mode, reportEntry );
+ return new TestAssumptionFailureEvent( memento.runMode, reportEntry );
default:
throw new IllegalStateException( "Unknown enum " + event );
}
@@ -379,116 +551,145 @@ public class EventConsumerThread extends CloseableDaemonThread
throw new IllegalStateException( "Missing a branch for the event type " + event );
}
- private static FrameCompletion frameCompleteness( List<String> tokens )
+ private static StackTraceWriter newTrace( String traceMessage, String smartTrimmedStackTrace, String stackTrace )
{
- if ( !tokens.isEmpty() && !MAGIC_NUMBER.equals( tokens.get( 0 ) ) )
- {
- return FrameCompletion.MALFORMED;
- }
+ boolean exists = traceMessage != null || stackTrace != null || smartTrimmedStackTrace != null;
+ return exists ? new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace ) : null;
+ }
+
+ static TestSetReportEntry newReportEntry( // ReportEntry:
+ String source, String sourceText, String name,
+ String nameText, String group, String message,
+ Integer timeElapsed,
+ // StackTraceWriter:
+ String traceMessage,
+ String smartTrimmedStackTrace, String stackTrace )
+ throws NumberFormatException
+ {
+ StackTraceWriter stackTraceWriter = newTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
+ return reportEntry( source, sourceText, name, nameText, group, stackTraceWriter, timeElapsed, message,
+ Collections.<String, String>emptyMap() );
+ }
- if ( tokens.size() >= 2 )
+ private static boolean isJvmError( String line )
+ {
+ String lineLower = line.toLowerCase();
+ for ( String errorPattern : JVM_ERROR_PATTERNS )
{
- String opcode = tokens.get( 1 );
- ForkedProcessEventType event = ForkedProcessEventType.byOpcode( opcode );
- if ( event == null )
- {
- return FrameCompletion.MALFORMED;
- }
- else if ( event.isControlCategory() )
- {
- return FrameCompletion.COMPLETE;
- }
- else if ( event.isConsoleErrorCategory() )
- {
- return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
- }
- else if ( event.isConsoleCategory() )
- {
- return tokens.size() == 4 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
- }
- else if ( event.isStandardStreamCategory() )
- {
- return tokens.size() == 5 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
- }
- else if ( event.isSysPropCategory() )
- {
- return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
- }
- else if ( event.isTestCategory() )
- {
- return tokens.size() == 14 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
- }
- else if ( event.isJvmExitError() )
+ if ( lineLower.contains( errorPattern ) )
{
- return tokens.size() == 6 ? FrameCompletion.COMPLETE : FrameCompletion.NOT_COMPLETE;
+ return true;
}
}
- return FrameCompletion.NOT_COMPLETE;
+ return false;
}
- static String decode( String line, Charset encoding )
+ private String readSegment( Memento memento ) throws IOException
{
- // ForkedChannelEncoder is encoding the stream with US_ASCII
- return line == null || "-".equals( line )
- ? null
- : new String( BASE64.decode( line.getBytes( US_ASCII ) ), encoding );
+ int startsWithPosition = memento.bb.position();
+ boolean streamContinues;
+
+ do
+ {
+ streamContinues = read( memento.bb, 1 ) != EOF;
+ }
+ while ( ( 0xff & memento.bb.get() ) != ':' );
+
+ int endsWithLimit = memento.bb.limit();
+ memento.bb.limit( memento.bb.position() - 1 );
+ memento.bb.position( startsWithPosition );
+
+ memento.cb.clear();
+
+ memento.decoder
+ .reset()
+ .decode( memento.bb, memento.cb, true );
+
+ memento.bb.position( memento.bb.position() + 1 );
+ memento.bb.limit( endsWithLimit );
+
+ String segment = memento.cb.flip().toString();
+ memento.cb.clear();
+ return segment;
}
- private static StackTraceWriter decodeTrace( Charset encoding, String encTraceMessage,
- String encSmartTrimmedStackTrace, String encStackTrace )
+ String readString( Memento memento ) throws IOException
{
- String traceMessage = decode( encTraceMessage, encoding );
- String stackTrace = decode( encStackTrace, encoding );
- String smartTrimmedStackTrace = decode( encSmartTrimmedStackTrace, encoding );
- boolean exists = traceMessage != null || stackTrace != null || smartTrimmedStackTrace != null;
- return exists ? new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace ) : null;
+ final CharBuffer chars = memento.cb;
+ final ByteBuffer buffer = memento.bb;
+ final int totalBytes = memento.bytesCounter;
+
+ int countDecodedBytes = 0;
+ int countReadBytes = 0;
+
+ buffer.clear();
+
+ int positionChars = chars.position();
+ int startPosition;
+ List<String> strings = new ArrayList<>();
+ do
+ {
+ startPosition = buffer.position();
+ buffer.limit( startPosition );
+ read( buffer, totalBytes - countReadBytes );
+ memento.decoder
+ .reset()
+ .decode( buffer, chars, countDecodedBytes + buffer.remaining() >= totalBytes );
+ final boolean hasDecodedNewBytes = chars.position() != positionChars;
+ if ( hasDecodedNewBytes )
+ {
+ countDecodedBytes += buffer.position() - startPosition;
+ positionChars = chars.position();
+ }
+ countReadBytes += buffer.limit() - startPosition;
+ if ( buffer.hasRemaining() )
+ {
+ buffer.compact();
+ }
+ strings.add( chars.flip().toString() );
+ chars.clear();
+ }
+ while ( countReadBytes < totalBytes );
+
+ return toString( strings );
}
- static TestSetReportEntry decodeReportEntry( Charset encoding,
- // ReportEntry:
- String encSource, String encSourceText, String encName,
- String encNameText, String encGroup, String encMessage,
- String encTimeElapsed,
- // StackTraceWriter:
- String encTraceMessage,
- String encSmartTrimmedStackTrace, String encStackTrace )
- throws NumberFormatException
+ int readInt( ByteBuffer bb ) throws IOException
{
- if ( encoding == null )
- {
- // corrupted or incomplete stream
- return null;
- }
-
- String source = decode( encSource, encoding );
- String sourceText = decode( encSourceText, encoding );
- String name = decode( encName, encoding );
- String nameText = decode( encNameText, encoding );
- String group = decode( encGroup, encoding );
- StackTraceWriter stackTraceWriter =
- decodeTrace( encoding, encTraceMessage, encSmartTrimmedStackTrace, encStackTrace );
- Integer elapsed = decodeToInteger( encTimeElapsed );
- String message = decode( encMessage, encoding );
- return reportEntry( source, sourceText, name, nameText,
- group, stackTraceWriter, elapsed, message, Collections.<String, String>emptyMap() );
+ read( bb, 4 );
+ return bb.getInt();
}
- static Integer decodeToInteger( String line )
+ private static String toString( List<String> strings )
{
- return line == null || "-".equals( line ) ? null : Integer.decode( line );
+ if ( strings.size() == 1 )
+ {
+ return strings.get( 0 );
+ }
+ StringBuilder concatenated = new StringBuilder();
+ for ( String s : strings )
+ {
+ concatenated.append( s );
+ }
+ return concatenated.toString();
}
- private static boolean isJvmError( String line )
+ static Map<Segment, ForkedProcessEventType> mapEventTypes()
{
- String lineLower = line.toLowerCase();
- for ( String errorPattern : JVM_ERROR_PATTERNS )
+ Map<Segment, ForkedProcessEventType> map = new HashMap<>();
+ for (ForkedProcessEventType e : ForkedProcessEventType.values() )
{
- if ( lineLower.contains( errorPattern ) )
- {
- return true;
- }
+ byte[] array = e.getOpcode().getBytes();
+ map.put( new Segment( array, 0, array.length ), e );
}
- return false;
+ return map;
+ }
+
+ enum StreamReadStatus
+ {
+ UNDERFLOW,
+ OVERFLOW,
+ EOF
}
/**
@@ -500,4 +701,160 @@ public class EventConsumerThread extends CloseableDaemonThread
COMPLETE,
MALFORMED
}
+
+ private enum SegmentType
+ {
+ RUN_MODE,
+ STRING_ENCODING,
+ BYTES_INT_COUNTER,
+ DATA_STRING,
+ DATA_INT,
+ END_OF_FRAME
+ }
+
+ /**
+ * This class avoids locking which gains the performance of this decoder.
+ */
+ private static class BufferedStream
+ {
+ private byte[] buffer;
+ private int count;
+
+ public BufferedStream( int capacity )
+ {
+ this.buffer = new byte[capacity];
+ }
+
+ void write( int b )
+ {
+ ensureCapacity( 1 );
+ buffer[count++] = (byte) b;
+ }
+
+ void write( ByteBuffer bb )
+ {
+ int size = bb.remaining();
+ if ( size > 0 )
+ {
+ ensureCapacity( size );
+ byte[] b = bb.array();
+ int pos = bb.arrayOffset() + bb.position();
+ System.arraycopy( b, pos, buffer, count, size );
+ count+= size;
+ bb.position( bb.position() + size );
+ }
+ }
+
+ boolean isEmpty()
+ {
+ return count != 0;
+ }
+
+ void reset()
+ {
+ count = 0;
+ }
+
+ String toString( Charset charset )
+ {
+ return new String( buffer, 0, count, charset );
+ }
+
+ private void ensureCapacity( int addCapacity )
+ {
+ int oldCapacity = buffer.length;
+ int exactCapacity = count + addCapacity;
+ if ( exactCapacity < 0 )
+ {
+ throw new OutOfMemoryError();
+ }
+
+ if ( oldCapacity < exactCapacity )
+ {
+ int newCapacity = oldCapacity << 1;
+ buffer = Arrays.copyOf( buffer, max( newCapacity, exactCapacity ) );
+ }
+ }
+ }
+
+ static class Memento
+ {
+ final CharsetDecoder decoder;
+ final BufferedStream segment = new BufferedStream( MAGIC_NUMBER.length() );
+ final List<Object> data = new ArrayList<>();
+ final CharBuffer cb = CharBuffer.allocate( 1024 );
+ final ByteBuffer bb = ByteBuffer.allocate( 1024 );
+ FrameCompletion frameCompletion;
+ ForkedProcessEventType eventType;
+ RunMode runMode;
+ Charset charset;
+ int bytesCounter;
+
+ Memento()
+ {
+ decoder = STREAM_ENCODING.newDecoder()
+ .onMalformedInput( REPLACE )
+ .onUnmappableCharacter( REPLACE );
+ }
+
+ void reset()
+ {
+ segment.reset();
+ frameCompletion = null;
+ }
+ }
+
+ static class Segment
+ {
+ private final byte[] array;
+ private final int fromIndex;
+ private final int length;
+ private final int hashCode;
+
+ public Segment( byte[] array, int fromIndex, int length )
+ {
+ this.array = array;
+ this.fromIndex = fromIndex;
+ this.length = length;
+
+ int hashCode = 0;
+ int i = fromIndex;
+ for (int loops = length >> 1; loops-- != 0; )
+ {
+ hashCode = 31 * hashCode + array[i++];
+ hashCode = 31 * hashCode + array[i++];
+ }
+ this.hashCode = i == fromIndex + length ? hashCode : 31 * hashCode + array[i];
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals( Object obj )
+ {
+ if ( !( obj instanceof Segment ) )
+ {
+ return false;
+ }
+
+ Segment that = (Segment) obj;
+ if ( that.length != length )
+ {
+ return false;
+ }
+
+ for ( int i = 0; i < length; i++ )
+ {
+ if ( that.array[that.fromIndex + i] != array[fromIndex + i] )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
new file mode 100644
index 0000000..0979399
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThreadTest.java
@@ -0,0 +1,492 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+import org.apache.maven.plugin.surefire.extensions.EventConsumerThread.Memento;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.apache.maven.surefire.extensions.ForkNodeArguments;
+import org.apache.maven.surefire.extensions.util.CountdownCloseable;
+import org.fest.assertions.Assertions;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.Mockito;
+
+import javax.annotation.Nonnull;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.Channel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
+
+import static java.nio.charset.CodingErrorAction.REPLACE;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThreadTest.StreamReadStatus.EOF;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThreadTest.StreamReadStatus.OVERFLOW;
+import static org.apache.maven.plugin.surefire.extensions.EventConsumerThreadTest.StreamReadStatus.UNDERFLOW;
+import static org.apache.maven.surefire.api.booter.Constants.STREAM_ENCODING;
+import static org.fest.assertions.Assertions.assertThat;
+//import static org.mockito.Mockito.mock;
+
+//@RunWith( Parameterized.class )
+public class EventConsumerThreadTest
+{
+ @Parameters
+ public static Iterable<Object> channels()
+ {
+ return Arrays.asList( (Object) complexEncodings() );
+ }
+
+ @Parameter
+ public Channel channel;
+
+ private static Channel complexEncodings()
+ {
+ byte[] bytes = new byte[]{(byte) -30, (byte) -126, (byte) -84, 'a', 'b', (byte) 0xc2, (byte) 0xa9, 'c'};
+ bytes = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".getBytes( UTF_8 );
+ return new Channel( bytes, 1 );
+ }
+
+ private static Channel complexEncodings( int chunkSize )
+ {
+ byte[] bytes = new byte[]{(byte) -30, (byte) -126, (byte) -84, 'a', 'b', (byte) 0xc2, (byte) 0xa9, 'c'};
+ bytes = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".getBytes( UTF_8 );
+ return new Channel( bytes, chunkSize );
+ }
+
+ @Test
+ public void test5() throws IOException, InterruptedException
+ {
+ /*final CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
+ .onMalformedInput( REPLACE )
+ .onUnmappableCharacter( REPLACE );
+ final CharBuffer chars = CharBuffer.allocate(5);
+
+ final int totalBytes = 7;
+
+ int countDecodedBytes = 0;
+ int countReadBytes = 0;
+ final ByteBuffer buffer = ByteBuffer.wrap(new byte[4]);
+
+ buffer.clear();
+
+ int positionChars = chars.position();
+ int startPosition;
+ List<String> strings = new ArrayList<>();
+ do
+ {
+ startPosition = buffer.position();
+ buffer.limit( startPosition );
+ read( buffer, totalBytes - countReadBytes );
+ decoder.decode(buffer, chars, countDecodedBytes >= totalBytes );
+ final boolean hasDecodedNewBytes = chars.position() != positionChars;
+ if ( hasDecodedNewBytes )
+ {
+ countDecodedBytes += buffer.position() - startPosition;
+ positionChars = chars.position();
+ }
+ countReadBytes += buffer.limit() - startPosition;
+ buffer.compact();
+ strings.add( chars.flip().toString() );
+ chars.clear();
+ }
+ while ( countReadBytes < totalBytes );
+ decoder.reset();
+
+ String s = toString( strings );*/
+ channel = complexEncodings(100);
+
+ Closeable c = new Closeable()
+ {
+ @Override
+ public void close() throws IOException
+ {
+
+ }
+ };
+
+ EventHandler eh = new EventHandler()
+ {
+ @Override
+ public void handleEvent( @Nonnull Object event )
+ {
+
+ }
+ };
+
+ ForkNodeArguments forkNodeArguments = new ForkNodeArguments()
+ {
+ @Nonnull
+ @Override
+ public String getSessionId()
+ {
+ return null;
+ }
+
+ @Override
+ public int getForkChannelId()
+ {
+ return 0;
+ }
+
+ @Nonnull
+ @Override
+ public File dumpStreamText( @Nonnull String text )
+ {
+ return null;
+ }
+
+ @Override
+ public void logWarningAtEnd( @Nonnull String text )
+ {
+
+ }
+
+ @Nonnull
+ @Override
+ public ConsoleLogger getConsoleLogger()
+ {
+ return null;
+ }
+ };
+
+ CountdownCloseable closeable = new CountdownCloseable( c, 0 );
+ EventConsumerThread thread = new EventConsumerThread( "t", channel, eh, closeable,
+ forkNodeArguments );
+ Memento memento = new Memento();
+ TimeUnit.SECONDS.sleep( 2 );
+ System.gc();
+ TimeUnit.SECONDS.sleep( 5 );
+ long l1 = System.currentTimeMillis();
+ for (int i = 0; i < 10_000_000; i++) {
+ //memento.cb.clear();
+ memento.bb.position(0);
+ memento.bb.limit(memento.bb.capacity());
+ memento.bytesCounter = 100;//7
+ memento.data.clear();
+ channel.reset();
+ thread.readString( memento );
+ }
+
+ long l2 = System.currentTimeMillis();
+ System.out.println(l2 - l1);
+ /*assertThat( s )
+ .isEqualTo( "€ab©" );*/
+ }
+
+ @Test
+ public void test6() throws InterruptedException
+ {
+ CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
+ .onMalformedInput( REPLACE )
+ .onUnmappableCharacter( REPLACE );
+ // CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
+ // CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
+ ByteBuffer buffer = ByteBuffer.wrap( "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".getBytes( UTF_8 ) );
+ CharBuffer chars = CharBuffer.allocate( 100 );
+ TimeUnit.SECONDS.sleep( 2 );
+ System.gc();
+ TimeUnit.SECONDS.sleep( 5 );
+ long l1 = System.currentTimeMillis();
+ for ( int i = 0; i < 10_000_000; i++ )
+ {
+ decoder
+ .reset()
+ .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
+ chars.flip().toString();//CharsetDecoder + toString = 91 nanos
+ buffer.clear();
+ chars.clear();
+ }
+ long l2 = System.currentTimeMillis();
+ System.out.println(l2 - l1);
+ }
+
+ @Test
+ public void test7() throws Exception {
+ byte[] b = {};
+ TimeUnit.SECONDS.sleep( 2 );
+ System.gc();
+ TimeUnit.SECONDS.sleep( 5 );
+ String s = null;
+ long l1 = System.currentTimeMillis();
+ for (int i = 0; i < 10_000_000; i++) {
+ s = new String(b, UTF_8);
+ }
+
+ long l2 = System.currentTimeMillis();
+ System.out.println(l2 - l1);
+ System.out.println(s);
+ }
+
+ @Test
+ public void test8() {
+ ForkedProcessEventType[] enums = ForkedProcessEventType.values();
+ SortedSet<Integer> s = new TreeSet<>();
+ for (ForkedProcessEventType e : enums) {
+ if (s.contains( e.getOpcode().length() )) {
+ System.out.println("obsahuje " + e + " s dlzkou " + e.getOpcode().length() + " s has codom " + e.getOpcode().hashCode());
+ }
+ s.add( e.getOpcode().length() );
+ }
+ }
+
+ @Test
+ public void test9() throws InterruptedException
+ {
+ ForkedProcessEventType[] enums = ForkedProcessEventType.values();
+ TreeMap<Integer, ForkedProcessEventType> map = new TreeMap<>();
+ for (ForkedProcessEventType e : enums) {
+ map.put( e.getOpcode().hashCode(), e );
+ }
+ TimeUnit.SECONDS.sleep( 2 );
+ System.gc();
+ TimeUnit.SECONDS.sleep( 5 );
+ ForkedProcessEventType s = null;
+ int hash = ForkedProcessEventType.BOOTERCODE_STDOUT.hashCode();
+ long l1 = System.currentTimeMillis();
+ for (int i = 0; i < 10_000_000; i++) {
+ s = map.get( hash );
+ }
+ long l2 = System.currentTimeMillis();
+ System.out.println(l2 - l1);
+ System.out.println(s);
+ }
+
+ @Test
+ public void test10() throws InterruptedException
+ {
+ ForkedProcessEventType[] enums = ForkedProcessEventType.values();
+ Map<Segment, ForkedProcessEventType> map = new HashMap<>();
+ for (ForkedProcessEventType e : enums) {
+ byte[] array = e.getOpcode().getBytes();
+ map.put( new Segment( array, 0, array.length ), e );
+ }
+ TimeUnit.SECONDS.sleep( 2 );
+ System.gc();
+ TimeUnit.SECONDS.sleep( 5 );
+ ForkedProcessEventType s = null;
+ byte[] array = ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes();
+ Segment segment = new Segment( array, 0, array.length );
+ long l1 = System.currentTimeMillis();
+ for (int i = 0; i < 10_000_000; i++) {
+ s = map.get( new Segment( array, 0, array.length ) ); // 33.7 nanos
+ }
+ long l2 = System.currentTimeMillis();
+ System.out.println(l2 - l1);
+ System.out.println(s);
+ }
+
+ @Test
+ public void test11() throws InterruptedException
+ {
+ CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
+ .onMalformedInput( REPLACE )
+ .onUnmappableCharacter( REPLACE );
+ ByteBuffer buffer = ByteBuffer.wrap( ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 ) );
+ CharBuffer chars = CharBuffer.allocate( 100 );
+ TimeUnit.SECONDS.sleep( 2 );
+ System.gc();
+ TimeUnit.SECONDS.sleep( 5 );
+ long l1 = System.currentTimeMillis();
+ for ( int i = 0; i < 10_000_000; i++ )
+ {
+ decoder
+ .reset()
+ .decode( buffer, chars, true );
+ String s = chars.flip().toString(); // 37 nanos = CharsetDecoder + toString
+ buffer.clear();
+ chars.clear();
+ ForkedProcessEventType.byOpcode( s ); // 65 nanos = CharsetDecoder + toString + byOpcode
+ }
+ long l2 = System.currentTimeMillis();
+ System.out.println(l2 - l1);
+ }
+
+ @Test
+ public void test12() throws InterruptedException
+ {
+ StringBuilder builder = new StringBuilder( 256 );
+ TimeUnit.SECONDS.sleep( 2 );
+ System.gc();
+ TimeUnit.SECONDS.sleep( 5 );
+ byte[] array = ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes();
+ long l1 = System.currentTimeMillis();
+ for (int i = 0; i < 10_000_000; i++) {
+ builder.setLength( 0 );
+ for ( byte b : array )
+ {
+ char c = (char) b;
+ if ( c == ':' ) break;
+ builder.append( c );
+ }
+ }
+ long l2 = System.currentTimeMillis();
+ System.out.println(l2 - l1);
+ System.out.println( builder.toString() );
+ }
+
+ private static class Segment {
+ private final byte[] array;
+ private final int fromIndex;
+ private final int length;
+ private final int hashCode;
+
+ public Segment( byte[] array, int fromIndex, int length )
+ {
+ this.array = array;
+ this.fromIndex = fromIndex;
+ this.length = length;
+
+ int hashCode = 0;
+ int i = fromIndex;
+ for (int loops = length >> 1; loops-- != 0; )
+ {
+ hashCode = 31 * hashCode + array[i++];
+ hashCode = 31 * hashCode + array[i++];
+ }
+ this.hashCode = i == fromIndex + length ? hashCode : 31 * hashCode + array[i];
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals( Object obj )
+ {
+ if ( !( obj instanceof Segment ) )
+ {
+ return false;
+ }
+
+ Segment that = (Segment) obj;
+ if ( that.length != length )
+ {
+ return false;
+ }
+
+ for ( int i = 0; i < length; i++ )
+ {
+ if ( that.array[that.fromIndex + i] != array[fromIndex + i] )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+
+ private static String toString( List<String> strings )
+ {
+ if ( strings.size() == 1 )
+ {
+ return strings.get( 0 );
+ }
+ StringBuilder concatenated = new StringBuilder();
+ for ( String s : strings )
+ {
+ concatenated.append( s );
+ }
+ return concatenated.toString();
+ }
+
+ private StreamReadStatus read( ByteBuffer buffer, int recommendedCount ) throws IOException
+ {
+ if ( buffer.remaining() >= recommendedCount && buffer.position() != 0 )
+ {
+ return OVERFLOW;
+ }
+ else
+ {
+ int mark = buffer.position();
+ buffer.position( buffer.limit() );
+ buffer.limit( buffer.capacity() );
+ boolean isEnd = false;
+ while ( !isEnd && buffer.position() - mark < recommendedCount && buffer.position() != buffer.limit() )
+ {
+ isEnd = channel.read( buffer ) == -1;
+ }
+ buffer.limit( buffer.position() );
+ buffer.position( mark );
+ return isEnd ? EOF : ( buffer.remaining() >= recommendedCount ? OVERFLOW : UNDERFLOW );
+ }
+ }
+
+ private static class Channel implements ReadableByteChannel
+ {
+ private final byte[] bytes;
+ private final int chunkSize;
+ private int i;
+
+ public Channel( byte[] bytes, int chunkSize )
+ {
+ this.bytes = bytes;
+ this.chunkSize = chunkSize;
+ }
+
+ public void reset()
+ {
+ i = 0;
+ }
+
+ @Override
+ public int read( ByteBuffer dst )
+ {
+ if ( i == bytes.length )
+ {
+ return -1;
+ }
+ else if ( dst.hasRemaining() )
+ {
+ int length = Math.min( chunkSize, bytes.length - i );
+ dst.put( bytes, i, length );
+ i += length;
+ return length;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return false;
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ }
+ }
+
+ enum StreamReadStatus
+ {
+ UNDERFLOW,
+ OVERFLOW,
+ EOF
+ }
+}
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java
index e84e5bc..ea81458 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/api/booter/ForkedProcessEventType.java
@@ -31,37 +31,261 @@ import static java.util.Collections.unmodifiableMap;
* @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
* @since 3.0.0-M4
*/
+@SuppressWarnings( "checkstyle:linelength" )
public enum ForkedProcessEventType
{
- BOOTERCODE_SYSPROPS( "sys-prop" ),
+ /**
+ * This is the opcode "sys-prop". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:sys-prop:RunMode:UTF-8:0xFFFFFFFF:key:0xFFFFFFFF:value:
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "sys-prop"
+ * </ul>
+ */
+ BOOTERCODE_SYSPROPS( "sys-prop" ),
+ /**
+ * This is the opcode "testset-starting". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:testset-starting:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "testset-starting"
+ * </ul>
+ */
BOOTERCODE_TESTSET_STARTING( "testset-starting" ),
+
+ /**
+ * This is the opcode "testset-completed". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:testset-completed:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "testset-completed"
+ * </ul>
+ */
BOOTERCODE_TESTSET_COMPLETED( "testset-completed" ),
+
+ /**
+ * This is the opcode "test-starting". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:test-starting:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "test-starting"
+ * </ul>
+ */
BOOTERCODE_TEST_STARTING( "test-starting" ),
+
+ /**
+ * This is the opcode "test-succeeded". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:test-succeeded:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "test-succeeded"
+ * </ul>
+ */
BOOTERCODE_TEST_SUCCEEDED( "test-succeeded" ),
+
+ /**
+ * This is the opcode "test-failed". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:test-failed:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "test-failed"
+ * </ul>
+ */
BOOTERCODE_TEST_FAILED( "test-failed" ),
+
+ /**
+ * This is the opcode "test-skipped". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:test-skipped:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "test-skipped"
+ * </ul>
+ */
BOOTERCODE_TEST_SKIPPED( "test-skipped" ),
+
+ /**
+ * This is the opcode "test-error". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:test-error:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "test-error"
+ * </ul>
+ */
BOOTERCODE_TEST_ERROR( "test-error" ),
+
+ /**
+ * This is the opcode "test-assumption-failure". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:test-assumption-failure:RunMode:UTF-8:0xFFFFFFFF:SourceName:0xFFFFFFFF:SourceText:0xFFFFFFFF:Name:0xFFFFFFFF:NameText:0xFFFFFFFF:Group:0xFFFFFFFF:Message:ElapsedTime (binary int):0xFFFFFFFF:LocalizedMessage:0xFFFFFFFF:SmartTrimmedStackTrace:0xFFFFFFFF:toStackTrace( stw, trimStackTraces ):
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "test-assumption-failure"
+ * </ul>
+ */
BOOTERCODE_TEST_ASSUMPTIONFAILURE( "test-assumption-failure" ),
+ /**
+ * This is the opcode "std-out-stream". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:std-out-stream:RunMode:UTF-8:0xFFFFFFFF:line:
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "std-out-stream"
+ * </ul>
+ */
BOOTERCODE_STDOUT( "std-out-stream" ),
+
+ /**
+ * This is the opcode "std-out-stream-new-line". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:std-out-stream-new-line:RunMode:UTF-8:0xFFFFFFFF:line:
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "std-out-stream-new-line"
+ * </ul>
+ */
BOOTERCODE_STDOUT_NEW_LINE( "std-out-stream-new-line" ),
+
+ /**
+ * This is the opcode "std-err-stream". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:std-err-stream:RunMode:UTF-8:0xFFFFFFFF:line:
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "std-err-stream"
+ * </ul>
+ */
BOOTERCODE_STDERR( "std-err-stream" ),
+
+ /**
+ * This is the opcode "std-err-stream-new-line". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:std-err-stream-new-line:RunMode:UTF-8:0xFFFFFFFF:line:
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "std-err-stream-new-line"
+ * </ul>
+ */
BOOTERCODE_STDERR_NEW_LINE( "std-err-stream-new-line" ),
+ /**
+ * This is the opcode "console-info-log". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:console-info-log:RunMode:UTF-8:0xFFFFFFFF:line:
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "console-info-log"
+ * </ul>
+ */
BOOTERCODE_CONSOLE_INFO( "console-info-log" ),
+
+ /**
+ * This is the opcode "console-debug-log". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:console-debug-log:RunMode:UTF-8:0xFFFFFFFF:line:
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "console-debug-log"
+ * </ul>
+ */
BOOTERCODE_CONSOLE_DEBUG( "console-debug-log" ),
+
+ /**
+ * This is the opcode "console-warning-log". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:console-warning-log:RunMode:UTF-8:0xFFFFFFFF:line:
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "console-warning-log"
+ * </ul>
+ */
BOOTERCODE_CONSOLE_WARNING( "console-warning-log" ),
+
+ /**
+ * This is the opcode "console-error-log". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:console-error-log:RunMode:UTF-8:0xFFFFFFFF:line:
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "console-error-log"
+ * </ul>
+ */
BOOTERCODE_CONSOLE_ERROR( "console-error-log" ),
+ /**
+ * This is the opcode "bye". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:bye:
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "bye"
+ * </ul>
+ */
BOOTERCODE_BYE( "bye" ),
+
+ /**
+ * This is the opcode "stop-on-next-test". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:stop-on-next-test:
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "stop-on-next-test"
+ * </ul>
+ */
BOOTERCODE_STOP_ON_NEXT_TEST( "stop-on-next-test" ),
+
+ /**
+ * This is the opcode "next-test". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:next-test:
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "next-test"
+ * </ul>
+ */
BOOTERCODE_NEXT_TEST( "next-test" ),
+ /**
+ * This is the opcode "jvm-exit-error". The frame is composed of segments and the separator characters ':'
+ * <pre>
+ * :maven-surefire-event:jvm-exit-error:
+ * </pre>
+ * The constructor with one argument:
+ * <ul>
+ * <li>the opcode is "jvm-exit-error"
+ * </ul>
+ */
BOOTERCODE_JVM_EXIT_ERROR( "jvm-exit-error" );
- public static final String MAGIC_NUMBER = "maven-surefire-event";
-
private static final Map<String, ForkedProcessEventType> EVENTS = events();
private static Map<String, ForkedProcessEventType> events()