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