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 java.io.PrintStream;
23  import java.util.Properties;
24  
25  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
26  import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
27  import org.apache.maven.surefire.report.ConsoleOutputReceiver;
28  import org.apache.maven.surefire.report.ConsoleStream;
29  import org.apache.maven.surefire.report.ReportEntry;
30  import org.apache.maven.surefire.report.RunListener;
31  import org.apache.maven.surefire.report.SafeThrowable;
32  import org.apache.maven.surefire.report.SimpleReportEntry;
33  import org.apache.maven.surefire.report.StackTraceWriter;
34  
35  import static java.lang.Integer.toHexString;
36  import static java.nio.charset.Charset.defaultCharset;
37  import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
38  import static org.apache.maven.surefire.util.internal.StringUtils.escapeBytesToPrintable;
39  import static org.apache.maven.surefire.util.internal.StringUtils.escapeToPrintable;
40  
41  /**
42   * Encodes the full output of the test run to the stdout stream.
43   * <p/>
44   * This class and the ForkClient contain the full definition of the
45   * "wire-level" protocol used by the forked process. The protocol
46   * is *not* part of any public api and may change without further
47   * notice.
48   * <p/>
49   * This class is threadsafe.
50   * <p/>
51   * The synchronization in the underlying PrintStream (target instance)
52   * is used to preserve thread safety of the output stream. To perform
53   * multiple writes/prints for a single request, they must
54   * synchronize on "target" variable in this class.
55   *
56   * @author Kristian Rosenvold
57   */
58  public class ForkingRunListener
59      implements RunListener, ConsoleLogger, ConsoleOutputReceiver, ConsoleStream
60  {
61      public static final byte BOOTERCODE_TESTSET_STARTING = (byte) '1';
62  
63      public static final byte BOOTERCODE_TESTSET_COMPLETED = (byte) '2';
64  
65      public static final byte BOOTERCODE_STDOUT = (byte) '3';
66  
67      public static final byte BOOTERCODE_STDERR = (byte) '4';
68  
69      public static final byte BOOTERCODE_TEST_STARTING = (byte) '5';
70  
71      public static final byte BOOTERCODE_TEST_SUCCEEDED = (byte) '6';
72  
73      public static final byte BOOTERCODE_TEST_ERROR = (byte) '7';
74  
75      public static final byte BOOTERCODE_TEST_FAILED = (byte) '8';
76  
77      public static final byte BOOTERCODE_TEST_SKIPPED = (byte) '9';
78  
79      public static final byte BOOTERCODE_TEST_ASSUMPTIONFAILURE = (byte) 'G';
80  
81      /**
82       * INFO logger
83       * @see ConsoleLogger#info(String)
84       */
85      public static final byte BOOTERCODE_CONSOLE = (byte) 'H';
86  
87      public static final byte BOOTERCODE_SYSPROPS = (byte) 'I';
88  
89      public static final byte BOOTERCODE_NEXT_TEST = (byte) 'N';
90  
91      public static final byte BOOTERCODE_STOP_ON_NEXT_TEST = (byte) 'S';
92  
93      /**
94       * ERROR logger
95       * @see ConsoleLogger#error(String)
96       */
97      public static final byte BOOTERCODE_ERROR = (byte) 'X';
98  
99      public static final byte BOOTERCODE_BYE = (byte) 'Z';
100 
101     /**
102      * DEBUG logger
103      * @see ConsoleLogger#debug(String)
104      */
105     public static final byte BOOTERCODE_DEBUG = (byte) 'D';
106 
107     /**
108      * WARNING logger
109      * @see ConsoleLogger#warning(String)
110      */
111     public static final byte BOOTERCODE_WARNING = (byte) 'W';
112 
113 
114     private final PrintStream target;
115 
116     private final int testSetChannelId;
117 
118     private final boolean trimStackTraces;
119 
120     private final byte[] stdOutHeader;
121 
122     private final byte[] stdErrHeader;
123 
124     public ForkingRunListener( PrintStream target, int testSetChannelId, boolean trimStackTraces )
125     {
126         this.target = target;
127         this.testSetChannelId = testSetChannelId;
128         this.trimStackTraces = trimStackTraces;
129         stdOutHeader = createHeader( BOOTERCODE_STDOUT, testSetChannelId );
130         stdErrHeader = createHeader( BOOTERCODE_STDERR, testSetChannelId );
131         sendProps();
132     }
133 
134     public void testSetStarting( ReportEntry report )
135     {
136         encodeAndWriteToTarget( toString( BOOTERCODE_TESTSET_STARTING, report, testSetChannelId ) );
137     }
138 
139     public void testSetCompleted( ReportEntry report )
140     {
141         encodeAndWriteToTarget( toString( BOOTERCODE_TESTSET_COMPLETED, report, testSetChannelId ) );
142     }
143 
144     public void testStarting( ReportEntry report )
145     {
146         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_STARTING, report, testSetChannelId ) );
147     }
148 
149     public void testSucceeded( ReportEntry report )
150     {
151         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_SUCCEEDED, report, testSetChannelId ) );
152     }
153 
154     public void testAssumptionFailure( ReportEntry report )
155     {
156         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_ASSUMPTIONFAILURE, report, testSetChannelId ) );
157     }
158 
159     public void testError( ReportEntry report )
160     {
161         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_ERROR, report, testSetChannelId ) );
162     }
163 
164     public void testFailed( ReportEntry report )
165     {
166         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_FAILED, report, testSetChannelId ) );
167     }
168 
169     public void testSkipped( ReportEntry report )
170     {
171         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_SKIPPED, report, testSetChannelId ) );
172     }
173 
174     public void testExecutionSkippedByUser()
175     {
176         encodeAndWriteToTarget( toString( BOOTERCODE_STOP_ON_NEXT_TEST, new SimpleReportEntry(), testSetChannelId ) );
177     }
178 
179     void sendProps()
180     {
181         Properties systemProperties = System.getProperties();
182 
183         if ( systemProperties != null )
184         {
185             for ( final String key : systemProperties.stringPropertyNames() )
186             {
187                 String value = systemProperties.getProperty( key );
188                 encodeAndWriteToTarget( toPropertyString( key, value == null ? "null" : value ) );
189             }
190         }
191     }
192 
193     public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
194     {
195         byte[] header = stdout ? stdOutHeader : stdErrHeader;
196         byte[] content =
197             new byte[buf.length * 3 + 1]; // Hex-escaping can be up to 3 times length of a regular byte.
198         int i = escapeBytesToPrintable( content, 0, buf, off, len );
199         content[i++] = (byte) '\n';
200         byte[] encodeBytes = new byte[header.length + i];
201         System.arraycopy( header, 0, encodeBytes, 0, header.length );
202         System.arraycopy( content, 0, encodeBytes, header.length, i );
203 
204         synchronized ( target ) // See notes about synchronization/thread safety in class javadoc
205         {
206             target.write( encodeBytes, 0, encodeBytes.length );
207             target.flush();
208             if ( target.checkError() )
209             {
210                 // We MUST NOT throw any exception from this method; otherwise we are in loop and CPU goes up:
211                 // ForkingRunListener -> Exception -> JUnit Notifier and RunListener -> ForkingRunListener -> Exception
212                 DumpErrorSingleton.getSingleton()
213                         .dumpStreamText( "Unexpected IOException with stream: " + new String( buf, off, len ) );
214             }
215         }
216     }
217 
218     public static byte[] createHeader( byte booterCode, int testSetChannel )
219     {
220         return encodeStringForForkCommunication( String.valueOf( (char) booterCode )
221                 + ','
222                 + Integer.toString( testSetChannel, 16 )
223                 + ',' + defaultCharset().name()
224                 + ',' );
225     }
226 
227     private void log( byte bootCode, String message )
228     {
229         if ( message != null )
230         {
231             StringBuilder sb = new StringBuilder( 7 + message.length() * 5 );
232             append( sb, bootCode ); comma( sb );
233             append( sb, toHexString( testSetChannelId ) ); comma( sb );
234             escapeToPrintable( sb, message );
235 
236             sb.append( '\n' );
237             encodeAndWriteToTarget( sb.toString() );
238         }
239     }
240 
241     public void debug( String message )
242     {
243         log( BOOTERCODE_DEBUG, message );
244     }
245 
246     public void info( String message )
247     {
248         log( BOOTERCODE_CONSOLE, message );
249     }
250 
251     public void warning( String message )
252     {
253         log( BOOTERCODE_WARNING, message );
254     }
255 
256     public void error( String message )
257     {
258         log( BOOTERCODE_ERROR, message );
259     }
260 
261     public void error( String message, Throwable t )
262     {
263         error( ConsoleLoggerUtils.toString( message, t ) );
264     }
265 
266     public void error( Throwable t )
267     {
268         error( null, t );
269     }
270 
271     private void encodeAndWriteToTarget( String string )
272     {
273         byte[] encodeBytes = encodeStringForForkCommunication( string );
274         synchronized ( target ) // See notes about synchronization/thread safety in class javadoc
275         {
276             target.write( encodeBytes, 0, encodeBytes.length );
277             target.flush();
278             if ( target.checkError() )
279             {
280                 // We MUST NOT throw any exception from this method; otherwise we are in loop and CPU goes up:
281                 // ForkingRunListener -> Exception -> JUnit Notifier and RunListener -> ForkingRunListener -> Exception
282                 DumpErrorSingleton.getSingleton().dumpStreamText( "Unexpected IOException: " + string );
283             }
284         }
285     }
286 
287     private String toPropertyString( String key, String value )
288     {
289         StringBuilder stringBuilder = new StringBuilder();
290 
291         append( stringBuilder, BOOTERCODE_SYSPROPS ); comma( stringBuilder );
292         append( stringBuilder, toHexString( testSetChannelId ) ); comma( stringBuilder );
293 
294         escapeToPrintable( stringBuilder, key );
295         comma( stringBuilder );
296         escapeToPrintable( stringBuilder, value );
297         stringBuilder.append( "\n" );
298         return stringBuilder.toString();
299     }
300 
301     private String toString( byte operationCode, ReportEntry reportEntry, int testSetChannelId )
302     {
303         StringBuilder stringBuilder = new StringBuilder();
304         append( stringBuilder, operationCode ); comma( stringBuilder );
305         append( stringBuilder, toHexString( testSetChannelId ) ); comma( stringBuilder );
306 
307         nullableEncoding( stringBuilder, reportEntry.getSourceName() );
308         comma( stringBuilder );
309         nullableEncoding( stringBuilder, reportEntry.getName() );
310         comma( stringBuilder );
311         nullableEncoding( stringBuilder, reportEntry.getGroup() );
312         comma( stringBuilder );
313         nullableEncoding( stringBuilder, reportEntry.getMessage() );
314         comma( stringBuilder );
315         nullableEncoding( stringBuilder, reportEntry.getElapsed() );
316         encode( stringBuilder, reportEntry.getStackTraceWriter() );
317         stringBuilder.append( "\n" );
318         return stringBuilder.toString();
319     }
320 
321     private static void comma( StringBuilder stringBuilder )
322     {
323         stringBuilder.append( "," );
324     }
325 
326     private ForkingRunListener append( StringBuilder stringBuilder, String message )
327     {
328         stringBuilder.append( encode( message ) );
329         return this;
330     }
331 
332     private ForkingRunListener append( StringBuilder stringBuilder, byte b )
333     {
334         stringBuilder.append( (char) b );
335         return this;
336     }
337 
338     private void nullableEncoding( StringBuilder stringBuilder, Integer source )
339     {
340         if ( source == null )
341         {
342             stringBuilder.append( "null" );
343         }
344         else
345         {
346             stringBuilder.append( source.toString() );
347         }
348     }
349 
350     private String encode( String source )
351     {
352         return source;
353     }
354 
355 
356     private static void nullableEncoding( StringBuilder stringBuilder, String source )
357     {
358         if ( source == null || source.length() == 0 )
359         {
360             stringBuilder.append( "null" );
361         }
362         else
363         {
364             escapeToPrintable( stringBuilder, source );
365         }
366     }
367 
368     private void encode( StringBuilder stringBuilder, StackTraceWriter stackTraceWriter )
369     {
370         encode( stringBuilder, stackTraceWriter, trimStackTraces );
371     }
372 
373     public static void encode( StringBuilder stringBuilder, StackTraceWriter stackTraceWriter, boolean trimStackTraces )
374     {
375         if ( stackTraceWriter != null )
376         {
377             comma( stringBuilder );
378             //noinspection ThrowableResultOfMethodCallIgnored
379             final SafeThrowable throwable = stackTraceWriter.getThrowable();
380             if ( throwable != null )
381             {
382                 String message = throwable.getLocalizedMessage();
383                 nullableEncoding( stringBuilder, message );
384             }
385             comma( stringBuilder );
386             nullableEncoding( stringBuilder, stackTraceWriter.smartTrimmedStackTrace() );
387             comma( stringBuilder );
388             nullableEncoding( stringBuilder, trimStackTraces
389                 ? stackTraceWriter.writeTrimmedTraceToString()
390                 : stackTraceWriter.writeTraceToString() );
391         }
392     }
393 
394     public void println( String message )
395     {
396         byte[] buf = message.getBytes();
397         println( buf, 0, buf.length );
398     }
399 
400     public void println( byte[] buf, int off, int len )
401     {
402         writeTestOutput( buf, off, len, true );
403     }
404 }