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