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.nio.charset.Charset;
24  import java.util.Enumeration;
25  import java.util.Properties;
26  
27  import org.apache.maven.surefire.report.ConsoleLogger;
28  import org.apache.maven.surefire.report.ConsoleOutputReceiver;
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  import org.apache.maven.surefire.util.internal.StringUtils;
35  
36  import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
37  
38  /**
39   * Encodes the full output of the test run to the stdout stream.
40   * <p/>
41   * This class and the ForkClient contain the full definition of the
42   * "wire-level" protocol used by the forked process. The protocol
43   * is *not* part of any public api and may change without further
44   * notice.
45   * <p/>
46   * This class is threadsafe.
47   * <p/>
48   * The synchronization in the underlying PrintStream (target instance)
49   * is used to preserve thread safety of the output stream. To perform
50   * multiple writes/prints for a single request, they must
51   * synchronize on "target" variable in this class.
52   *
53   * @author Kristian Rosenvold
54   */
55  public class ForkingRunListener
56      implements RunListener, ConsoleLogger, ConsoleOutputReceiver
57  {
58      public static final byte BOOTERCODE_TESTSET_STARTING = (byte) '1';
59  
60      public static final byte BOOTERCODE_TESTSET_COMPLETED = (byte) '2';
61  
62      public static final byte BOOTERCODE_STDOUT = (byte) '3';
63  
64      public static final byte BOOTERCODE_STDERR = (byte) '4';
65  
66      public static final byte BOOTERCODE_TEST_STARTING = (byte) '5';
67  
68      public static final byte BOOTERCODE_TEST_SUCCEEDED = (byte) '6';
69  
70      public static final byte BOOTERCODE_TEST_ERROR = (byte) '7';
71  
72      public static final byte BOOTERCODE_TEST_FAILED = (byte) '8';
73  
74      public static final byte BOOTERCODE_TEST_SKIPPED = (byte) '9';
75  
76      public static final byte BOOTERCODE_TEST_ASSUMPTIONFAILURE = (byte) 'G';
77  
78      public static final byte BOOTERCODE_CONSOLE = (byte) 'H';
79  
80      public static final byte BOOTERCODE_SYSPROPS = (byte) 'I';
81  
82      public static final byte BOOTERCODE_NEXT_TEST = (byte) 'N';
83  
84      public static final byte BOOTERCODE_STOP_ON_NEXT_TEST = (byte) 'S';
85  
86      public static final byte BOOTERCODE_ERROR = (byte) 'X';
87  
88      public static final byte BOOTERCODE_BYE = (byte) 'Z';
89  
90      private final PrintStream target;
91  
92      private final int testSetChannelId;
93  
94      private final boolean trimStackTraces;
95  
96      private final byte[] stdOutHeader;
97  
98      private final byte[] stdErrHeader;
99  
100     public ForkingRunListener( PrintStream target, int testSetChannelId, boolean trimStackTraces )
101     {
102         this.target = target;
103         this.testSetChannelId = testSetChannelId;
104         this.trimStackTraces = trimStackTraces;
105         stdOutHeader = createHeader( BOOTERCODE_STDOUT, testSetChannelId );
106         stdErrHeader = createHeader( BOOTERCODE_STDERR, testSetChannelId );
107         sendProps();
108     }
109 
110     public void testSetStarting( ReportEntry report )
111     {
112         encodeAndWriteToTarget( toString( BOOTERCODE_TESTSET_STARTING, report, testSetChannelId ) );
113     }
114 
115     public void testSetCompleted( ReportEntry report )
116     {
117         encodeAndWriteToTarget( toString( BOOTERCODE_TESTSET_COMPLETED, report, testSetChannelId ) );
118     }
119 
120     public void testStarting( ReportEntry report )
121     {
122         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_STARTING, report, testSetChannelId ) );
123     }
124 
125     public void testSucceeded( ReportEntry report )
126     {
127         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_SUCCEEDED, report, testSetChannelId ) );
128     }
129 
130     public void testAssumptionFailure( ReportEntry report )
131     {
132         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_ASSUMPTIONFAILURE, report, testSetChannelId ) );
133     }
134 
135     public void testError( ReportEntry report )
136     {
137         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_ERROR, report, testSetChannelId ) );
138     }
139 
140     public void testFailed( ReportEntry report )
141     {
142         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_FAILED, report, testSetChannelId ) );
143     }
144 
145     public void testSkipped( ReportEntry report )
146     {
147         encodeAndWriteToTarget( toString( BOOTERCODE_TEST_SKIPPED, report, testSetChannelId ) );
148     }
149 
150     public void testExecutionSkippedByUser()
151     {
152         encodeAndWriteToTarget( toString( BOOTERCODE_STOP_ON_NEXT_TEST, new SimpleReportEntry(), testSetChannelId ) );
153     }
154 
155     void sendProps()
156     {
157         Properties systemProperties = System.getProperties();
158 
159         if ( systemProperties != null )
160         {
161             for ( Enumeration<?> propertyKeys = systemProperties.propertyNames(); propertyKeys.hasMoreElements(); )
162             {
163                 String key = (String) propertyKeys.nextElement();
164                 String value = systemProperties.getProperty( key );
165                 encodeAndWriteToTarget( toPropertyString( key, value == null ? "null" : value ) );
166             }
167         }
168     }
169 
170     public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
171     {
172         byte[] header = stdout ? stdOutHeader : stdErrHeader;
173         byte[] content =
174             new byte[buf.length * 3 + 1]; // Hex-escaping can be up to 3 times length of a regular byte.
175         int i = StringUtils.escapeBytesToPrintable( content, 0, buf, off, len );
176         content[i++] = (byte) '\n';
177 
178         synchronized ( target ) // See notes about synchronization/thread safety in class javadoc
179         {
180             target.write( header, 0, header.length );
181             target.write( content, 0, i );
182         }
183     }
184 
185     public static byte[] createHeader( byte booterCode, int testSetChannel )
186     {
187         return encodeStringForForkCommunication( String.valueOf( (char) booterCode )
188                 + ','
189                 + Integer.toString( testSetChannel, 16 )
190                 + ',' + Charset.defaultCharset().name()
191                 + ',' );
192     }
193 
194     public void info( String message )
195     {
196         if ( message != null )
197         {
198             StringBuilder sb = new StringBuilder( 7 + message.length() * 5 );
199             append( sb, BOOTERCODE_CONSOLE ); comma( sb );
200             append( sb, Integer.toHexString( testSetChannelId ) ); comma( sb );
201             StringUtils.escapeToPrintable( sb, message );
202 
203             sb.append( '\n' );
204             encodeAndWriteToTarget( sb.toString() );
205         }
206     }
207 
208     private void encodeAndWriteToTarget( String string )
209     {
210         byte[] encodeBytes = encodeStringForForkCommunication( string );
211         synchronized ( target ) // See notes about synchronization/thread safety in class javadoc
212         {
213             target.write( encodeBytes, 0, encodeBytes.length );
214         }
215     }
216 
217     private String toPropertyString( String key, String value )
218     {
219         StringBuilder stringBuilder = new StringBuilder();
220 
221         append( stringBuilder, BOOTERCODE_SYSPROPS ); comma( stringBuilder );
222         append( stringBuilder, Integer.toHexString( testSetChannelId ) ); comma( stringBuilder );
223 
224         StringUtils.escapeToPrintable( stringBuilder, key );
225         comma( stringBuilder );
226         StringUtils.escapeToPrintable( stringBuilder, value );
227         stringBuilder.append( "\n" );
228         return stringBuilder.toString();
229     }
230 
231     private String toString( byte operationCode, ReportEntry reportEntry, int testSetChannelId )
232     {
233         StringBuilder stringBuilder = new StringBuilder();
234         append( stringBuilder, operationCode ); comma( stringBuilder );
235         append( stringBuilder, Integer.toHexString( testSetChannelId ) ); comma( stringBuilder );
236 
237         nullableEncoding( stringBuilder, reportEntry.getSourceName() );
238         comma( stringBuilder );
239         nullableEncoding( stringBuilder, reportEntry.getName() );
240         comma( stringBuilder );
241         nullableEncoding( stringBuilder, reportEntry.getGroup() );
242         comma( stringBuilder );
243         nullableEncoding( stringBuilder, reportEntry.getMessage() );
244         comma( stringBuilder );
245         nullableEncoding( stringBuilder, reportEntry.getElapsed() );
246         encode( stringBuilder, reportEntry.getStackTraceWriter() );
247         stringBuilder.append( "\n" );
248         return stringBuilder.toString();
249     }
250 
251     private static void comma( StringBuilder stringBuilder )
252     {
253         stringBuilder.append( "," );
254     }
255 
256     private ForkingRunListener append( StringBuilder stringBuilder, String message )
257     {
258         stringBuilder.append( encode( message ) );
259         return this;
260     }
261 
262     private ForkingRunListener append( StringBuilder stringBuilder, byte b )
263     {
264         stringBuilder.append( (char) b );
265         return this;
266     }
267 
268     private void nullableEncoding( StringBuilder stringBuilder, Integer source )
269     {
270         if ( source == null )
271         {
272             stringBuilder.append( "null" );
273         }
274         else
275         {
276             stringBuilder.append( source.toString() );
277         }
278     }
279 
280     private String encode( String source )
281     {
282         return source;
283     }
284 
285 
286     private static void nullableEncoding( StringBuilder stringBuilder, String source )
287     {
288         if ( source == null || source.length() == 0 )
289         {
290             stringBuilder.append( "null" );
291         }
292         else
293         {
294             StringUtils.escapeToPrintable( stringBuilder, source );
295         }
296     }
297 
298     private void encode( StringBuilder stringBuilder, StackTraceWriter stackTraceWriter )
299     {
300         encode( stringBuilder, stackTraceWriter, trimStackTraces );
301     }
302 
303     public static void encode( StringBuilder stringBuilder, StackTraceWriter stackTraceWriter, boolean trimStackTraces )
304     {
305         if ( stackTraceWriter != null )
306         {
307             comma( stringBuilder );
308             //noinspection ThrowableResultOfMethodCallIgnored
309             final SafeThrowable throwable = stackTraceWriter.getThrowable();
310             if ( throwable != null )
311             {
312                 String message = throwable.getLocalizedMessage();
313                 nullableEncoding( stringBuilder, message );
314             }
315             comma( stringBuilder );
316             nullableEncoding( stringBuilder, stackTraceWriter.smartTrimmedStackTrace() );
317             comma( stringBuilder );
318             nullableEncoding( stringBuilder, trimStackTraces
319                 ? stackTraceWriter.writeTrimmedTraceToString()
320                 : stackTraceWriter.writeTraceToString() );
321         }
322     }
323 }