View Javadoc
1   package org.apache.maven.surefire.booter.spi;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
23  import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
24  import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
25  import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
26  import org.apache.maven.surefire.api.report.ReportEntry;
27  import org.apache.maven.surefire.api.report.RunMode;
28  import org.apache.maven.surefire.api.report.SafeThrowable;
29  import org.apache.maven.surefire.api.report.StackTraceWriter;
30  import org.apache.maven.surefire.shared.codec.binary.Base64;
31  import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
32  
33  import javax.annotation.Nonnull;
34  import java.io.IOException;
35  import java.nio.ByteBuffer;
36  import java.nio.channels.ClosedChannelException;
37  import java.nio.charset.Charset;
38  import java.util.Map;
39  import java.util.Map.Entry;
40  import java.util.concurrent.atomic.AtomicBoolean;
41  
42  import static java.nio.charset.StandardCharsets.US_ASCII;
43  import static java.nio.charset.StandardCharsets.UTF_8;
44  import static java.util.Objects.requireNonNull;
45  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_BYE;
46  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
47  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR;
48  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO;
49  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING;
50  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR;
51  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST;
52  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR;
53  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE;
54  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
55  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE;
56  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST;
57  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
58  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED;
59  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING;
60  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
61  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR;
62  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED;
63  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED;
64  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
65  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
66  import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.MAGIC_NUMBER;
67  import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
68  import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAILURE;
69  
70  /**
71   * magic number : opcode : run mode [: opcode specific data]*
72   * <br>
73   *
74   * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
75   * @since 3.0.0-M4
76   */
77  public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEncoder
78  {
79      private static final Base64 BASE64 = new Base64();
80      private static final Charset STREAM_ENCODING = US_ASCII;
81      private static final Charset STRING_ENCODING = UTF_8;
82  
83      private final WritableBufferedByteChannel out;
84      private final RunMode runMode;
85      private final AtomicBoolean trouble = new AtomicBoolean();
86      private volatile boolean onExit;
87  
88      public LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out )
89      {
90          this( out, NORMAL_RUN );
91      }
92  
93      protected LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out, @Nonnull RunMode runMode )
94      {
95          this.out = requireNonNull( out );
96          this.runMode = requireNonNull( runMode );
97      }
98  
99      @Override
100     public MasterProcessChannelEncoder asRerunMode() // todo apply this and rework providers
101     {
102         return new LegacyMasterProcessChannelEncoder( out, RERUN_TEST_AFTER_FAILURE );
103     }
104 
105     @Override
106     public MasterProcessChannelEncoder asNormalMode()
107     {
108         return new LegacyMasterProcessChannelEncoder( out, NORMAL_RUN );
109     }
110 
111     @Override
112     public boolean checkError()
113     {
114         return trouble.get();
115     }
116 
117     @Override
118     public void onJvmExit()
119     {
120         onExit = true;
121         encodeAndPrintEvent( new StringBuilder( "\n" ), true );
122     }
123 
124     @Override
125     public void sendSystemProperties( Map<String, String> sysProps )
126     {
127         for ( Entry<String, String> entry : sysProps.entrySet() )
128         {
129             String key = entry.getKey();
130             String value = entry.getValue();
131             StringBuilder event = encode( BOOTERCODE_SYSPROPS, runMode, key, value );
132             encodeAndPrintEvent( event, false );
133         }
134     }
135 
136     @Override
137     public void testSetStarting( ReportEntry reportEntry, boolean trimStackTraces )
138     {
139         encode( BOOTERCODE_TESTSET_STARTING, runMode, reportEntry, trimStackTraces, true );
140     }
141 
142     @Override
143     public void testSetCompleted( ReportEntry reportEntry, boolean trimStackTraces )
144     {
145         encode( BOOTERCODE_TESTSET_COMPLETED, runMode, reportEntry, trimStackTraces, true );
146     }
147 
148     @Override
149     public void testStarting( ReportEntry reportEntry, boolean trimStackTraces )
150     {
151         encode( BOOTERCODE_TEST_STARTING, runMode, reportEntry, trimStackTraces, true );
152     }
153 
154     @Override
155     public void testSucceeded( ReportEntry reportEntry, boolean trimStackTraces )
156     {
157         encode( BOOTERCODE_TEST_SUCCEEDED, runMode, reportEntry, trimStackTraces, true );
158     }
159 
160     @Override
161     public void testFailed( ReportEntry reportEntry, boolean trimStackTraces )
162     {
163         encode( BOOTERCODE_TEST_FAILED, runMode, reportEntry, trimStackTraces, true );
164     }
165 
166     @Override
167     public void testSkipped( ReportEntry reportEntry, boolean trimStackTraces )
168     {
169         encode( BOOTERCODE_TEST_SKIPPED, runMode, reportEntry, trimStackTraces, true );
170     }
171 
172     @Override
173     public void testError( ReportEntry reportEntry, boolean trimStackTraces )
174     {
175         encode( BOOTERCODE_TEST_ERROR, runMode, reportEntry, trimStackTraces, true );
176     }
177 
178     @Override
179     public void testAssumptionFailure( ReportEntry reportEntry, boolean trimStackTraces )
180     {
181         encode( BOOTERCODE_TEST_ASSUMPTIONFAILURE, runMode, reportEntry, trimStackTraces, true );
182     }
183 
184     @Override
185     public void stdOut( String msg, boolean newLine )
186     {
187         ForkedProcessEventType event = newLine ? BOOTERCODE_STDOUT_NEW_LINE : BOOTERCODE_STDOUT;
188         setOutErr( event.getOpcode(), msg );
189     }
190 
191     @Override
192     public void stdErr( String msg, boolean newLine )
193     {
194         ForkedProcessEventType event = newLine ? BOOTERCODE_STDERR_NEW_LINE : BOOTERCODE_STDERR;
195         setOutErr( event.getOpcode(), msg );
196     }
197 
198     private void setOutErr( String eventType, String message )
199     {
200         String base64Message = toBase64( message );
201         StringBuilder event = encodeMessage( eventType, runMode.geRunName(), base64Message );
202         encodeAndPrintEvent( event, false );
203     }
204 
205     @Override
206     public void consoleInfoLog( String msg )
207     {
208         StringBuilder event = print( BOOTERCODE_CONSOLE_INFO.getOpcode(), msg );
209         encodeAndPrintEvent( event, true );
210     }
211 
212     @Override
213     public void consoleErrorLog( String msg )
214     {
215         StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null );
216         encode( encoded, msg, null, null );
217         encodeAndPrintEvent( encoded, true );
218     }
219 
220     @Override
221     public void consoleErrorLog( Throwable t )
222     {
223         consoleErrorLog( t.getLocalizedMessage(), t );
224     }
225 
226     @Override
227     public void consoleErrorLog( String msg, Throwable t )
228     {
229         StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null );
230         encode( encoded, msg, null, ConsoleLoggerUtils.toString( t ) );
231         encodeAndPrintEvent( encoded, true );
232     }
233 
234     @Override
235     public void consoleErrorLog( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
236     {
237         error( stackTraceWriter, trimStackTraces, BOOTERCODE_CONSOLE_ERROR, true );
238     }
239 
240     @Override
241     public void consoleDebugLog( String msg )
242     {
243         StringBuilder event = print( BOOTERCODE_CONSOLE_DEBUG.getOpcode(), msg );
244         encodeAndPrintEvent( event, true );
245     }
246 
247     @Override
248     public void consoleWarningLog( String msg )
249     {
250         StringBuilder event = print( BOOTERCODE_CONSOLE_WARNING.getOpcode(), msg );
251         encodeAndPrintEvent( event, true );
252     }
253 
254     @Override
255     public void bye()
256     {
257         encodeOpcode( BOOTERCODE_BYE, true );
258     }
259 
260     @Override
261     public void stopOnNextTest()
262     {
263         encodeOpcode( BOOTERCODE_STOP_ON_NEXT_TEST, true );
264     }
265 
266     @Override
267     public void acquireNextTest()
268     {
269         encodeOpcode( BOOTERCODE_NEXT_TEST, true );
270     }
271 
272     @Override
273     public void sendExitError( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
274     {
275         error( stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR, true );
276     }
277 
278     private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEventType event,
279                         @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately )
280     {
281         StringBuilder encoded = encodeHeader( event.getOpcode(), null );
282         encode( encoded, stackTraceWriter, trimStackTraces );
283         encodeAndPrintEvent( encoded, sendImmediately );
284     }
285 
286     private void encode( ForkedProcessEventType operation, RunMode runMode, ReportEntry reportEntry,
287                          boolean trimStackTraces, @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately )
288     {
289         StringBuilder event = encode( operation.getOpcode(), runMode.geRunName(), reportEntry, trimStackTraces );
290         encodeAndPrintEvent( event, sendImmediately );
291     }
292 
293     private void encodeOpcode( ForkedProcessEventType operation, boolean sendImmediately )
294     {
295         StringBuilder event = encodeOpcode( operation.getOpcode(), null );
296         encodeAndPrintEvent( event, sendImmediately );
297     }
298 
299     private void encodeAndPrintEvent( StringBuilder event, boolean sendImmediately )
300     {
301         try
302         {
303             //noinspection ResultOfMethodCallIgnored
304             Thread.interrupted();
305 
306             byte[] array = event.append( '\n' )
307                 .toString()
308                 .getBytes( STREAM_ENCODING );
309 
310             ByteBuffer bb = ByteBuffer.wrap( array );
311 
312             if ( sendImmediately )
313             {
314                 out.write( bb );
315             }
316             else
317             {
318                 out.writeBuffered( bb );
319             }
320         }
321         catch ( ClosedChannelException e )
322         {
323             if ( !onExit )
324             {
325                 DumpErrorSingleton.getSingleton()
326                     .dumpException( e, "Channel closed while writing the event '" + event + "'." );
327             }
328         }
329         catch ( IOException e )
330         {
331             if ( trouble.compareAndSet( false, true ) )
332             {
333                 DumpErrorSingleton.getSingleton()
334                     .dumpException( e );
335             }
336         }
337     }
338 
339     static StringBuilder encode( ForkedProcessEventType operation, RunMode runMode, String... args )
340     {
341         StringBuilder encodedTo = encodeHeader( operation.getOpcode(), runMode.geRunName() );
342 
343         for ( int i = 0; i < args.length; )
344         {
345             String arg = args[i++];
346             encodedTo.append( toBase64( arg ) )
347                 .append( ':' );
348         }
349         return encodedTo;
350     }
351 
352     static void encode( StringBuilder encoded, StackTraceWriter stw, boolean trimStackTraces )
353     {
354         SafeThrowable throwable = stw == null ? null : stw.getThrowable();
355         String message = throwable == null ? null : throwable.getLocalizedMessage();
356         String smartStackTrace = stw == null ? null : stw.smartTrimmedStackTrace();
357         String stackTrace = stw == null ? null : toStackTrace( stw, trimStackTraces );
358         encode( encoded, message, smartStackTrace, stackTrace );
359     }
360 
361     private static void encode( StringBuilder encoded, String message, String smartStackTrace, String stackTrace )
362     {
363         encoded.append( toBase64( message ) )
364             .append( ':' )
365             .append( toBase64( smartStackTrace ) )
366             .append( ':' )
367             .append( toBase64( stackTrace ) )
368             .append( ':' );
369     }
370 
371     /**
372      * Used operations:<br>
373      * <ul>
374      * <li>{@link ForkedProcessEventType#BOOTERCODE_TESTSET_STARTING},</li>
375      * <li>{@link ForkedProcessEventType#BOOTERCODE_TESTSET_COMPLETED},</li>
376      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_STARTING},</li>
377      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_SUCCEEDED},</li>
378      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_FAILED},</li>
379      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ERROR},</li>
380      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_SKIPPED},</li>
381      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ASSUMPTIONFAILURE}.</li>
382      * </ul>
383      */
384     static StringBuilder encode( String operation, String runMode, ReportEntry reportEntry, boolean trimStackTraces )
385     {
386         StringBuilder encodedTo = encodeHeader( operation, runMode )
387                 .append( toBase64( reportEntry.getSourceName() ) )
388                 .append( ':' )
389                 .append( toBase64( reportEntry.getSourceText() ) )
390                 .append( ':' )
391                 .append( toBase64( reportEntry.getName() ) )
392                 .append( ':' )
393                 .append( toBase64( reportEntry.getNameText() ) )
394                 .append( ':' )
395                 .append( toBase64( reportEntry.getGroup() ) )
396                 .append( ':' )
397                 .append( toBase64( reportEntry.getMessage() ) )
398                 .append( ':' )
399                 .append( reportEntry.getElapsed() == null ? "-" : reportEntry.getElapsed().toString() )
400                 .append( ':' );
401 
402         encode( encodedTo, reportEntry.getStackTraceWriter(), trimStackTraces );
403 
404         return encodedTo;
405     }
406 
407     /**
408      * Used in {@link #consoleInfoLog(String)}, {@link #consoleErrorLog(String)}, {@link #consoleDebugLog(String)},
409      * {@link #consoleWarningLog(String)} and private methods extending the buffer.
410      */
411     StringBuilder print( String operation, String... msgs )
412     {
413         String[] encodedMsgs = new String[msgs.length];
414         for ( int i = 0; i < encodedMsgs.length; i++ )
415         {
416             String msg = msgs[i];
417             encodedMsgs[i] = toBase64( msg );
418         }
419         return encodeMessage( operation, null, encodedMsgs );
420     }
421 
422     static StringBuilder encodeMessage( String operation, String runMode, String... encodedMsgs )
423     {
424         StringBuilder builder = encodeHeader( operation, runMode );
425         for ( String encodedMsg : encodedMsgs )
426         {
427             builder.append( encodedMsg ).append( ':' );
428 
429         }
430         return builder;
431     }
432 
433     static StringBuilder encodeHeader( String operation, String runMode )
434     {
435         return encodeOpcode( operation, runMode )
436             .append( STRING_ENCODING.name() )
437             .append( ':' );
438     }
439 
440     /**
441      * Used in {@link #bye()}, {@link #stopOnNextTest()} and {@link #encodeOpcode(ForkedProcessEventType, boolean)}
442      * and private methods extending the buffer.
443      *
444      * @param operation opcode
445      * @param runMode   run mode
446      * @return encoded event
447      */
448     static StringBuilder encodeOpcode( String operation, String runMode )
449     {
450         StringBuilder s = new StringBuilder( 128 )
451             .append( ':' )
452             .append( MAGIC_NUMBER )
453             .append( ':' )
454             .append( operation )
455             .append( ':' );
456 
457         return runMode == null ? s : s.append( runMode ).append( ':' );
458     }
459 
460     private static String toStackTrace( StackTraceWriter stw, boolean trimStackTraces )
461     {
462         return trimStackTraces ? stw.writeTrimmedTraceToString() : stw.writeTraceToString();
463     }
464 
465     static String toBase64( String msg )
466     {
467         return msg == null ? "-" : new String( BASE64.encode( msg.getBytes( STRING_ENCODING ) ), STREAM_ENCODING );
468     }
469 }