| package org.apache.maven.surefire.booter.spi; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils; |
| import org.apache.maven.surefire.api.booter.DumpErrorSingleton; |
| import org.apache.maven.surefire.api.booter.ForkedProcessEventType; |
| import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder; |
| import org.apache.maven.surefire.api.report.ReportEntry; |
| import org.apache.maven.surefire.api.report.RunMode; |
| import org.apache.maven.surefire.api.report.SafeThrowable; |
| import org.apache.maven.surefire.api.report.StackTraceWriter; |
| import org.apache.maven.surefire.shared.codec.binary.Base64; |
| import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel; |
| |
| import javax.annotation.Nonnull; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.ClosedChannelException; |
| import java.nio.charset.Charset; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| import static java.nio.charset.StandardCharsets.US_ASCII; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| import static java.util.Objects.requireNonNull; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_BYE; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED; |
| import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.MAGIC_NUMBER; |
| import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN; |
| import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAILURE; |
| |
| /** |
| * magic number : opcode : run mode [: opcode specific data]* |
| * <br> |
| * |
| * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a> |
| * @since 3.0.0-M4 |
| */ |
| public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEncoder |
| { |
| private static final Base64 BASE64 = new Base64(); |
| private static final Charset STREAM_ENCODING = US_ASCII; |
| private static final Charset STRING_ENCODING = UTF_8; |
| |
| private final WritableBufferedByteChannel out; |
| private final RunMode runMode; |
| private final AtomicBoolean trouble = new AtomicBoolean(); |
| private volatile boolean onExit; |
| |
| public LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out ) |
| { |
| this( out, NORMAL_RUN ); |
| } |
| |
| protected LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out, @Nonnull RunMode runMode ) |
| { |
| this.out = requireNonNull( out ); |
| this.runMode = requireNonNull( runMode ); |
| } |
| |
| @Override |
| public MasterProcessChannelEncoder asRerunMode() // todo apply this and rework providers |
| { |
| return new LegacyMasterProcessChannelEncoder( out, RERUN_TEST_AFTER_FAILURE ); |
| } |
| |
| @Override |
| public MasterProcessChannelEncoder asNormalMode() |
| { |
| return new LegacyMasterProcessChannelEncoder( out, NORMAL_RUN ); |
| } |
| |
| @Override |
| public boolean checkError() |
| { |
| return trouble.get(); |
| } |
| |
| @Override |
| public void onJvmExit() |
| { |
| onExit = true; |
| encodeAndPrintEvent( new StringBuilder( "\n" ), true ); |
| } |
| |
| @Override |
| public void sendSystemProperties( Map<String, String> sysProps ) |
| { |
| for ( Entry<String, String> entry : sysProps.entrySet() ) |
| { |
| String key = entry.getKey(); |
| String value = entry.getValue(); |
| StringBuilder event = encode( BOOTERCODE_SYSPROPS, runMode, key, value ); |
| encodeAndPrintEvent( event, false ); |
| } |
| } |
| |
| @Override |
| public void testSetStarting( ReportEntry reportEntry, boolean trimStackTraces ) |
| { |
| encode( BOOTERCODE_TESTSET_STARTING, runMode, reportEntry, trimStackTraces, true ); |
| } |
| |
| @Override |
| public void testSetCompleted( ReportEntry reportEntry, boolean trimStackTraces ) |
| { |
| encode( BOOTERCODE_TESTSET_COMPLETED, runMode, reportEntry, trimStackTraces, true ); |
| } |
| |
| @Override |
| public void testStarting( ReportEntry reportEntry, boolean trimStackTraces ) |
| { |
| encode( BOOTERCODE_TEST_STARTING, runMode, reportEntry, trimStackTraces, true ); |
| } |
| |
| @Override |
| public void testSucceeded( ReportEntry reportEntry, boolean trimStackTraces ) |
| { |
| encode( BOOTERCODE_TEST_SUCCEEDED, runMode, reportEntry, trimStackTraces, true ); |
| } |
| |
| @Override |
| public void testFailed( ReportEntry reportEntry, boolean trimStackTraces ) |
| { |
| encode( BOOTERCODE_TEST_FAILED, runMode, reportEntry, trimStackTraces, true ); |
| } |
| |
| @Override |
| public void testSkipped( ReportEntry reportEntry, boolean trimStackTraces ) |
| { |
| encode( BOOTERCODE_TEST_SKIPPED, runMode, reportEntry, trimStackTraces, true ); |
| } |
| |
| @Override |
| public void testError( ReportEntry reportEntry, boolean trimStackTraces ) |
| { |
| encode( BOOTERCODE_TEST_ERROR, runMode, reportEntry, trimStackTraces, true ); |
| } |
| |
| @Override |
| public void testAssumptionFailure( ReportEntry reportEntry, boolean trimStackTraces ) |
| { |
| encode( BOOTERCODE_TEST_ASSUMPTIONFAILURE, runMode, reportEntry, trimStackTraces, true ); |
| } |
| |
| @Override |
| public void stdOut( String msg, boolean newLine ) |
| { |
| ForkedProcessEventType event = newLine ? BOOTERCODE_STDOUT_NEW_LINE : BOOTERCODE_STDOUT; |
| setOutErr( event.getOpcode(), msg ); |
| } |
| |
| @Override |
| public void stdErr( String msg, boolean newLine ) |
| { |
| ForkedProcessEventType event = newLine ? BOOTERCODE_STDERR_NEW_LINE : BOOTERCODE_STDERR; |
| setOutErr( event.getOpcode(), msg ); |
| } |
| |
| private void setOutErr( String eventType, String message ) |
| { |
| String base64Message = toBase64( message ); |
| StringBuilder event = encodeMessage( eventType, runMode.geRunName(), base64Message ); |
| encodeAndPrintEvent( event, false ); |
| } |
| |
| @Override |
| public void consoleInfoLog( String msg ) |
| { |
| StringBuilder event = print( BOOTERCODE_CONSOLE_INFO.getOpcode(), msg ); |
| encodeAndPrintEvent( event, true ); |
| } |
| |
| @Override |
| public void consoleErrorLog( String msg ) |
| { |
| StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null ); |
| encode( encoded, msg, null, null ); |
| encodeAndPrintEvent( encoded, true ); |
| } |
| |
| @Override |
| public void consoleErrorLog( Throwable t ) |
| { |
| consoleErrorLog( t.getLocalizedMessage(), t ); |
| } |
| |
| @Override |
| public void consoleErrorLog( String msg, Throwable t ) |
| { |
| StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null ); |
| encode( encoded, msg, null, ConsoleLoggerUtils.toString( t ) ); |
| encodeAndPrintEvent( encoded, true ); |
| } |
| |
| @Override |
| public void consoleErrorLog( StackTraceWriter stackTraceWriter, boolean trimStackTraces ) |
| { |
| error( stackTraceWriter, trimStackTraces, BOOTERCODE_CONSOLE_ERROR, true ); |
| } |
| |
| @Override |
| public void consoleDebugLog( String msg ) |
| { |
| StringBuilder event = print( BOOTERCODE_CONSOLE_DEBUG.getOpcode(), msg ); |
| encodeAndPrintEvent( event, true ); |
| } |
| |
| @Override |
| public void consoleWarningLog( String msg ) |
| { |
| StringBuilder event = print( BOOTERCODE_CONSOLE_WARNING.getOpcode(), msg ); |
| encodeAndPrintEvent( event, true ); |
| } |
| |
| @Override |
| public void bye() |
| { |
| encodeOpcode( BOOTERCODE_BYE, true ); |
| } |
| |
| @Override |
| public void stopOnNextTest() |
| { |
| encodeOpcode( BOOTERCODE_STOP_ON_NEXT_TEST, true ); |
| } |
| |
| @Override |
| public void acquireNextTest() |
| { |
| encodeOpcode( BOOTERCODE_NEXT_TEST, true ); |
| } |
| |
| @Override |
| public void sendExitError( StackTraceWriter stackTraceWriter, boolean trimStackTraces ) |
| { |
| error( stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR, true ); |
| } |
| |
| private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEventType event, |
| @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately ) |
| { |
| StringBuilder encoded = encodeHeader( event.getOpcode(), null ); |
| encode( encoded, stackTraceWriter, trimStackTraces ); |
| encodeAndPrintEvent( encoded, sendImmediately ); |
| } |
| |
| private void encode( ForkedProcessEventType operation, RunMode runMode, ReportEntry reportEntry, |
| boolean trimStackTraces, @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately ) |
| { |
| StringBuilder event = encode( operation.getOpcode(), runMode.geRunName(), reportEntry, trimStackTraces ); |
| encodeAndPrintEvent( event, sendImmediately ); |
| } |
| |
| private void encodeOpcode( ForkedProcessEventType operation, boolean sendImmediately ) |
| { |
| StringBuilder event = encodeOpcode( operation.getOpcode(), null ); |
| encodeAndPrintEvent( event, sendImmediately ); |
| } |
| |
| private void encodeAndPrintEvent( StringBuilder event, boolean sendImmediately ) |
| { |
| try |
| { |
| //noinspection ResultOfMethodCallIgnored |
| Thread.interrupted(); |
| |
| byte[] array = event.append( '\n' ) |
| .toString() |
| .getBytes( STREAM_ENCODING ); |
| |
| ByteBuffer bb = ByteBuffer.wrap( array ); |
| |
| if ( sendImmediately ) |
| { |
| out.write( bb ); |
| } |
| else |
| { |
| out.writeBuffered( bb ); |
| } |
| } |
| catch ( ClosedChannelException e ) |
| { |
| if ( !onExit ) |
| { |
| DumpErrorSingleton.getSingleton() |
| .dumpException( e, "Channel closed while writing the event '" + event + "'." ); |
| } |
| } |
| catch ( IOException e ) |
| { |
| if ( trouble.compareAndSet( false, true ) ) |
| { |
| DumpErrorSingleton.getSingleton() |
| .dumpException( e ); |
| } |
| } |
| } |
| |
| static StringBuilder encode( ForkedProcessEventType operation, RunMode runMode, String... args ) |
| { |
| StringBuilder encodedTo = encodeHeader( operation.getOpcode(), runMode.geRunName() ); |
| |
| for ( int i = 0; i < args.length; ) |
| { |
| String arg = args[i++]; |
| encodedTo.append( toBase64( arg ) ) |
| .append( ':' ); |
| } |
| return encodedTo; |
| } |
| |
| static void encode( StringBuilder encoded, StackTraceWriter stw, boolean trimStackTraces ) |
| { |
| SafeThrowable throwable = stw == null ? null : stw.getThrowable(); |
| String message = throwable == null ? null : throwable.getLocalizedMessage(); |
| String smartStackTrace = stw == null ? null : stw.smartTrimmedStackTrace(); |
| String stackTrace = stw == null ? null : toStackTrace( stw, trimStackTraces ); |
| encode( encoded, message, smartStackTrace, stackTrace ); |
| } |
| |
| private static void encode( StringBuilder encoded, String message, String smartStackTrace, String stackTrace ) |
| { |
| encoded.append( toBase64( message ) ) |
| .append( ':' ) |
| .append( toBase64( smartStackTrace ) ) |
| .append( ':' ) |
| .append( toBase64( stackTrace ) ) |
| .append( ':' ); |
| } |
| |
| /** |
| * Used operations:<br> |
| * <ul> |
| * <li>{@link ForkedProcessEventType#BOOTERCODE_TESTSET_STARTING},</li> |
| * <li>{@link ForkedProcessEventType#BOOTERCODE_TESTSET_COMPLETED},</li> |
| * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_STARTING},</li> |
| * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_SUCCEEDED},</li> |
| * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_FAILED},</li> |
| * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ERROR},</li> |
| * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_SKIPPED},</li> |
| * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ASSUMPTIONFAILURE}.</li> |
| * </ul> |
| */ |
| static StringBuilder encode( String operation, String runMode, ReportEntry reportEntry, boolean trimStackTraces ) |
| { |
| StringBuilder encodedTo = encodeHeader( operation, runMode ) |
| .append( toBase64( reportEntry.getSourceName() ) ) |
| .append( ':' ) |
| .append( toBase64( reportEntry.getSourceText() ) ) |
| .append( ':' ) |
| .append( toBase64( reportEntry.getName() ) ) |
| .append( ':' ) |
| .append( toBase64( reportEntry.getNameText() ) ) |
| .append( ':' ) |
| .append( toBase64( reportEntry.getGroup() ) ) |
| .append( ':' ) |
| .append( toBase64( reportEntry.getMessage() ) ) |
| .append( ':' ) |
| .append( reportEntry.getElapsed() == null ? "-" : reportEntry.getElapsed().toString() ) |
| .append( ':' ); |
| |
| encode( encodedTo, reportEntry.getStackTraceWriter(), trimStackTraces ); |
| |
| return encodedTo; |
| } |
| |
| /** |
| * Used in {@link #consoleInfoLog(String)}, {@link #consoleErrorLog(String)}, {@link #consoleDebugLog(String)}, |
| * {@link #consoleWarningLog(String)} and private methods extending the buffer. |
| */ |
| StringBuilder print( String operation, String... msgs ) |
| { |
| String[] encodedMsgs = new String[msgs.length]; |
| for ( int i = 0; i < encodedMsgs.length; i++ ) |
| { |
| String msg = msgs[i]; |
| encodedMsgs[i] = toBase64( msg ); |
| } |
| return encodeMessage( operation, null, encodedMsgs ); |
| } |
| |
| static StringBuilder encodeMessage( String operation, String runMode, String... encodedMsgs ) |
| { |
| StringBuilder builder = encodeHeader( operation, runMode ); |
| for ( String encodedMsg : encodedMsgs ) |
| { |
| builder.append( encodedMsg ).append( ':' ); |
| |
| } |
| return builder; |
| } |
| |
| static StringBuilder encodeHeader( String operation, String runMode ) |
| { |
| return encodeOpcode( operation, runMode ) |
| .append( STRING_ENCODING.name() ) |
| .append( ':' ); |
| } |
| |
| /** |
| * Used in {@link #bye()}, {@link #stopOnNextTest()} and {@link #encodeOpcode(ForkedProcessEventType, boolean)} |
| * and private methods extending the buffer. |
| * |
| * @param operation opcode |
| * @param runMode run mode |
| * @return encoded event |
| */ |
| static StringBuilder encodeOpcode( String operation, String runMode ) |
| { |
| StringBuilder s = new StringBuilder( 128 ) |
| .append( ':' ) |
| .append( MAGIC_NUMBER ) |
| .append( ':' ) |
| .append( operation ) |
| .append( ':' ); |
| |
| return runMode == null ? s : s.append( runMode ).append( ':' ); |
| } |
| |
| private static String toStackTrace( StackTraceWriter stw, boolean trimStackTraces ) |
| { |
| return trimStackTraces ? stw.writeTrimmedTraceToString() : stw.writeTraceToString(); |
| } |
| |
| static String toBase64( String msg ) |
| { |
| return msg == null ? "-" : new String( BASE64.encode( msg.getBytes( STRING_ENCODING ) ), STREAM_ENCODING ); |
| } |
| } |