View Javadoc
1   package org.apache.maven.surefire.booter;
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.commons.codec.binary.Base64;
23  import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
24  import org.apache.maven.surefire.report.ReportEntry;
25  import org.apache.maven.surefire.report.RunMode;
26  import org.apache.maven.surefire.report.SafeThrowable;
27  import org.apache.maven.surefire.report.StackTraceWriter;
28  
29  import java.io.IOException;
30  import java.io.OutputStream;
31  import java.nio.charset.Charset;
32  import java.util.Map;
33  import java.util.Map.Entry;
34  
35  import static java.nio.charset.StandardCharsets.US_ASCII;
36  import static java.nio.charset.StandardCharsets.UTF_8;
37  import static org.apache.maven.surefire.booter.ForkedProcessEvent.MAGIC_NUMBER;
38  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_SYSPROPS;
39  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDERR;
40  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDERR_NEW_LINE;
41  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDOUT;
42  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STDOUT_NEW_LINE;
43  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_BYE;
44  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_ERROR;
45  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_DEBUG;
46  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_INFO;
47  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_CONSOLE_WARNING;
48  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_NEXT_TEST;
49  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_STOP_ON_NEXT_TEST;
50  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
51  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_ERROR;
52  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_FAILED;
53  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_SKIPPED;
54  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_STARTING;
55  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TEST_SUCCEEDED;
56  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TESTSET_COMPLETED;
57  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_TESTSET_STARTING;
58  import static org.apache.maven.surefire.booter.ForkedProcessEvent.BOOTERCODE_JVM_EXIT_ERROR;
59  import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
60  import static org.apache.maven.surefire.report.RunMode.RERUN_TEST_AFTER_FAILURE;
61  import static java.util.Objects.requireNonNull;
62  
63  /**
64   * magic number : opcode : run mode [: opcode specific data]*
65   * <br>
66   *
67   * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
68   * @since 3.0.0-M4
69   */
70  public final class ForkedChannelEncoder
71  {
72      private static final Base64 BASE64 = new Base64();
73      private static final Charset STREAM_ENCODING = US_ASCII;
74      private static final Charset STRING_ENCODING = UTF_8;
75  
76      private final OutputStream out;
77      private final RunMode runMode;
78      private volatile boolean trouble;
79  
80      public ForkedChannelEncoder( OutputStream out )
81      {
82          this( out, NORMAL_RUN );
83      }
84  
85      private ForkedChannelEncoder( OutputStream out, RunMode runMode )
86      {
87          this.out = requireNonNull( out );
88          this.runMode = requireNonNull( runMode );
89      }
90  
91      public ForkedChannelEncoder asRerunMode() // todo apply this and rework providers
92      {
93          return new ForkedChannelEncoder( out, RERUN_TEST_AFTER_FAILURE );
94      }
95  
96      public ForkedChannelEncoder asNormalMode()
97      {
98          return new ForkedChannelEncoder( out, NORMAL_RUN );
99      }
100 
101     public boolean checkError()
102     {
103         return trouble;
104     }
105 
106     public void sendSystemProperties( Map<String, String> sysProps )
107     {
108         for ( Entry<String, String> entry : sysProps.entrySet() )
109         {
110             String key = entry.getKey();
111             String value = entry.getValue();
112             StringBuilder event = encode( BOOTERCODE_SYSPROPS, runMode, key, value );
113             encodeAndPrintEvent( event );
114         }
115     }
116 
117     public void testSetStarting( ReportEntry reportEntry, boolean trimStackTraces )
118     {
119         encode( BOOTERCODE_TESTSET_STARTING, runMode, reportEntry, trimStackTraces );
120     }
121 
122     public void testSetCompleted( ReportEntry reportEntry, boolean trimStackTraces )
123     {
124         encode( BOOTERCODE_TESTSET_COMPLETED, runMode, reportEntry, trimStackTraces );
125     }
126 
127     public void testStarting( ReportEntry reportEntry, boolean trimStackTraces )
128     {
129         encode( BOOTERCODE_TEST_STARTING, runMode, reportEntry, trimStackTraces );
130     }
131 
132     public void testSucceeded( ReportEntry reportEntry, boolean trimStackTraces )
133     {
134         encode( BOOTERCODE_TEST_SUCCEEDED, runMode, reportEntry, trimStackTraces );
135     }
136 
137     public void testFailed( ReportEntry reportEntry, boolean trimStackTraces )
138     {
139         encode( BOOTERCODE_TEST_FAILED, runMode, reportEntry, trimStackTraces );
140     }
141 
142     public void testSkipped( ReportEntry reportEntry, boolean trimStackTraces )
143     {
144         encode( BOOTERCODE_TEST_SKIPPED, runMode, reportEntry, trimStackTraces );
145     }
146 
147     public void testError( ReportEntry reportEntry, boolean trimStackTraces )
148     {
149         encode( BOOTERCODE_TEST_ERROR, runMode, reportEntry, trimStackTraces );
150     }
151 
152     public void testAssumptionFailure( ReportEntry reportEntry, boolean trimStackTraces )
153     {
154         encode( BOOTERCODE_TEST_ASSUMPTIONFAILURE, runMode, reportEntry, trimStackTraces );
155     }
156 
157     public void stdOut( String msg, boolean newLine )
158     {
159         ForkedProcessEvent event = newLine ? BOOTERCODE_STDOUT_NEW_LINE : BOOTERCODE_STDOUT;
160         setOutErr( event.getOpcode(), msg );
161     }
162 
163     public void stdErr( String msg, boolean newLine )
164     {
165         ForkedProcessEvent event = newLine ? BOOTERCODE_STDERR_NEW_LINE : BOOTERCODE_STDERR;
166         setOutErr( event.getOpcode(), msg );
167     }
168 
169     private void setOutErr( String eventType, String message )
170     {
171         String base64Message = toBase64( message );
172         StringBuilder event = encodeMessage( eventType, runMode.geRunName(), base64Message );
173         encodeAndPrintEvent( event );
174     }
175 
176     public void consoleInfoLog( String msg )
177     {
178         StringBuilder event = print( BOOTERCODE_CONSOLE_INFO.getOpcode(), msg );
179         encodeAndPrintEvent( event );
180     }
181 
182     public void consoleErrorLog( String msg )
183     {
184         StringBuilder event = print( BOOTERCODE_CONSOLE_ERROR.getOpcode(), msg );
185         encodeAndPrintEvent( event );
186     }
187 
188     public void consoleErrorLog( Throwable t )
189     {
190         consoleErrorLog( t.getLocalizedMessage(), t );
191     }
192 
193     public void consoleErrorLog( String msg, Throwable t )
194     {
195         StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null );
196         encode( encoded, msg, null, ConsoleLoggerUtils.toString( t ) );
197         encodeAndPrintEvent( encoded );
198     }
199 
200     public void consoleErrorLog( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
201     {
202         error( stackTraceWriter, trimStackTraces, BOOTERCODE_CONSOLE_ERROR );
203     }
204 
205     public void consoleDebugLog( String msg )
206     {
207         StringBuilder event = print( BOOTERCODE_CONSOLE_DEBUG.getOpcode(), msg );
208         encodeAndPrintEvent( event );
209     }
210 
211     public void consoleWarningLog( String msg )
212     {
213         StringBuilder event = print( BOOTERCODE_CONSOLE_WARNING.getOpcode(), msg );
214         encodeAndPrintEvent( event );
215     }
216 
217     public void bye()
218     {
219         encodeOpcode( BOOTERCODE_BYE );
220     }
221 
222     public void stopOnNextTest()
223     {
224         encodeOpcode( BOOTERCODE_STOP_ON_NEXT_TEST );
225     }
226 
227     public void acquireNextTest()
228     {
229         encodeOpcode( BOOTERCODE_NEXT_TEST );
230     }
231 
232     public void sendExitEvent( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
233     {
234         error( stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR );
235     }
236 
237     private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEvent event )
238     {
239         StringBuilder encoded = encodeHeader( event.getOpcode(), null );
240         encode( encoded, stackTraceWriter, trimStackTraces );
241         encodeAndPrintEvent( encoded );
242     }
243 
244     private void encode( ForkedProcessEvent operation, RunMode runMode, ReportEntry reportEntry,
245                          boolean trimStackTraces )
246     {
247         StringBuilder event = encode( operation.getOpcode(), runMode.geRunName(), reportEntry, trimStackTraces );
248         encodeAndPrintEvent( event );
249     }
250 
251     private void encodeOpcode( ForkedProcessEvent operation )
252     {
253         StringBuilder event = encodeOpcode( operation.getOpcode(), null );
254         encodeAndPrintEvent( event );
255     }
256 
257     private void encodeAndPrintEvent( StringBuilder command )
258     {
259         byte[] array = command.append( '\n' ).toString().getBytes( STREAM_ENCODING );
260         synchronized ( out )
261         {
262             try
263             {
264                 out.write( array );
265                 out.flush();
266             }
267             catch ( IOException e )
268             {
269                 DumpErrorSingleton.getSingleton().dumpException( e );
270                 trouble = true;
271             }
272         }
273     }
274 
275     static StringBuilder encode( ForkedProcessEvent operation, RunMode runMode, String... args )
276     {
277         StringBuilder encodedTo = encodeHeader( operation.getOpcode(), runMode.geRunName() )
278                                           .append( ':' );
279 
280         for ( int i = 0; i < args.length; )
281         {
282             String arg = args[i++];
283             encodedTo.append( toBase64( arg ) );
284             if ( i != args.length )
285             {
286                 encodedTo.append( ':' );
287             }
288         }
289         return encodedTo;
290     }
291 
292     static void encode( StringBuilder encoded, StackTraceWriter stw, boolean trimStackTraces )
293     {
294         SafeThrowable throwable = stw == null ? null : stw.getThrowable();
295         String message = throwable == null ? null : throwable.getLocalizedMessage();
296         String smartStackTrace = stw == null ? null : stw.smartTrimmedStackTrace();
297         String stackTrace = stw == null ? null : toStackTrace( stw, trimStackTraces );
298         encode( encoded, message, smartStackTrace, stackTrace );
299     }
300 
301     private static void encode( StringBuilder encoded, String message, String smartStackTrace, String stackTrace )
302     {
303         encoded.append( ':' )
304                 .append( toBase64( message ) )
305                 .append( ':' )
306                 .append( toBase64( smartStackTrace ) )
307                 .append( ':' )
308                 .append( toBase64( stackTrace ) );
309     }
310 
311     /**
312      * Used operations:<br>
313      * <ul>
314      * <li>{@link ForkedProcessEvent#BOOTERCODE_TESTSET_STARTING},</li>
315      * <li>{@link ForkedProcessEvent#BOOTERCODE_TESTSET_COMPLETED},</li>
316      * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_STARTING},</li>
317      * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_SUCCEEDED},</li>
318      * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_FAILED},</li>
319      * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_ERROR},</li>
320      * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_SKIPPED},</li>
321      * <li>{@link ForkedProcessEvent#BOOTERCODE_TEST_ASSUMPTIONFAILURE}.</li>
322      * </ul>
323      */
324     static StringBuilder encode( String operation, String runMode, ReportEntry reportEntry,
325                                          boolean trimStackTraces )
326     {
327         StringBuilder encodedTo = encodeHeader( operation, runMode )
328                 .append( ':' )
329                 .append( toBase64( reportEntry.getSourceName() ) )
330                 .append( ':' )
331                 .append( toBase64( reportEntry.getSourceText() ) )
332                 .append( ':' )
333                 .append( toBase64( reportEntry.getName() ) )
334                 .append( ':' )
335                 .append( toBase64( reportEntry.getNameText() ) )
336                 .append( ':' )
337                 .append( toBase64( reportEntry.getGroup() ) )
338                 .append( ':' )
339                 .append( toBase64( reportEntry.getMessage() ) )
340                 .append( ':' )
341                 .append( reportEntry.getElapsed() == null ? "-" : reportEntry.getElapsed().toString() );
342 
343         encode( encodedTo, reportEntry.getStackTraceWriter(), trimStackTraces );
344 
345         return encodedTo;
346     }
347 
348     /**
349      * Used in {@link #consoleInfoLog(String)}, {@link #consoleErrorLog(String)}, {@link #consoleDebugLog(String)},
350      * {@link #consoleWarningLog(String)} and private methods extending the buffer.
351      */
352     StringBuilder print( String operation, String... msgs )
353     {
354         String[] encodedMsgs = new String[msgs.length];
355         for ( int i = 0; i < encodedMsgs.length; i++ )
356         {
357             String msg = msgs[i];
358             encodedMsgs[i] = toBase64( msg );
359         }
360         return encodeMessage( operation, null, encodedMsgs );
361     }
362 
363     static StringBuilder encodeMessage( String operation, String runMode, String... encodedMsgs )
364     {
365         StringBuilder builder = encodeHeader( operation, runMode );
366         for ( String encodedMsg : encodedMsgs )
367         {
368             builder.append( ':' )
369                     .append( encodedMsg );
370 
371         }
372         return builder;
373     }
374 
375     static StringBuilder encodeHeader( String operation, String runMode )
376     {
377         return encodeOpcode( operation, runMode )
378                        .append( ':' )
379                        .append( STRING_ENCODING.name() );
380     }
381 
382     /**
383      * Used in {@link #bye()}, {@link #stopOnNextTest()} and {@link #encodeOpcode(ForkedProcessEvent)}
384      * and private methods extending the buffer.
385      *
386      * @param operation opcode
387      * @param runMode   run mode
388      * @return encoded command
389      */
390     static StringBuilder encodeOpcode( String operation, String runMode )
391     {
392         StringBuilder s = new StringBuilder( 128 )
393                 .append( MAGIC_NUMBER )
394                 .append( operation );
395 
396         return runMode == null ? s : s.append( ':' ).append( runMode );
397     }
398 
399     private static String toStackTrace( StackTraceWriter stw, boolean trimStackTraces )
400     {
401         return trimStackTraces ? stw.writeTrimmedTraceToString() : stw.writeTraceToString();
402     }
403 
404     static String toBase64( String msg )
405     {
406         return msg == null ? "-" : new String( BASE64.encode( msg.getBytes( STRING_ENCODING ) ), STREAM_ENCODING );
407     }
408 }